초기화 지연Lazy Initialization

개체 초기화 지연 은 개체를 처음 사용할 때까지 생성이 지연된다는 의미입니다.Lazy initialization of an object means that its creation is deferred until it is first used. 이 항목의 경우 초기화 지연지연 인스턴스화 는 동의어입니다. 초기화 지연은 주로 성능을 향상 시키고 불필요 한 계산을 방지 하 고 프로그램 메모리 요구 사항을 줄이는 데 사용 됩니다.(For this topic, the terms lazy initialization and lazy instantiation are synonymous.) Lazy initialization is primarily used to improve performance, avoid wasteful computation, and reduce program memory requirements. 다음은 가장 일반적인 시나리오입니다.These are the most common scenarios:

  • 만드는 데 비용이 많이 드는 개체가 있으며 프로그램에서 이 개체를 사용하지 않는 경우가 있습니다.When you have an object that is expensive to create, and the program might not use it. 예를 들어 초기화되기 위해 데이터베이스 연결이 필요한 큰 Order 개체 배열이 포함된 Orders 속성이 있는 Customer 개체가 메모리에 있다고 가정합니다.For example, assume that you have in memory a Customer object that has an Orders property that contains a large array of Order objects that, to be initialized, requires a database connection. 사용자는 주문을 표시하거나 계산에서 데이터를 사용하도록 요청하지 않으면 시스템 메모리 또는 계산 주기를 사용하여 만들 이유가 없습니다.If the user never asks to display the Orders or use the data in a computation, then there is no reason to use system memory or computing cycles to create it. Lazy<Orders>를 사용하여 Orders 개체의 초기화 지연을 선언하면 개체를 사용하지 않을 때 시스템 리소스 낭비를 방지할 수 있습니다.By using Lazy<Orders> to declare the Orders object for lazy initialization, you can avoid wasting system resources when the object is not used.

  • 만드는 데 비용이 많이 드는 개체가 있고 다른 비용이 많이 드는 작업이 완료될 때까지 생성을 지연시키려는 경우가 있습니다.When you have an object that is expensive to create, and you want to defer its creation until after other expensive operations have been completed. 예를 들어, 프로그램을 시작할 때 여러 개체 인스턴스를 로드하지만 그중 일부만 즉시 필요합니다.For example, assume that your program loads several object instances when it starts, but only some of them are required immediately. 필요한 개체를 만들 때까지 필요하지 않은 개체의 초기화를 지연하여 프로그램의 시작 성능을 향상시킬 수 있습니다.You can improve the startup performance of the program by deferring initialization of the objects that are not required until the required objects have been created.

초기화 지연을 수행하도록 고유 코드를 작성할 수 있지만 Lazy<T>를 대신 사용하는 것이 좋습니다.Although you can write your own code to perform lazy initialization, we recommend that you use Lazy<T> instead. Lazy<T> 및 관련 유형에서도 스레드로부터 안전을 지원하고 일관된 예외 전파 정책을 제공합니다.Lazy<T> and its related types also support thread-safety and provide a consistent exception propagation policy.

다음 표에는 다양한 시나리오에서 초기화 지연을 사용하도록 .NET Framework 버전 4에서 제공하는 유형이 나열되어 있습니다.The following table lists the types that the .NET Framework version 4 provides to enable lazy initialization in different scenarios.

유형Type DescriptionDescription
Lazy<T> 클래스 라이브러리 또는 사용자 정의 형식에 대한 초기화 지연 의미 체계를 제공하는 래퍼 클래스입니다.A wrapper class that provides lazy initialization semantics for any class library or user-defined type.
ThreadLocal<T> 스레드-로컬 기반으로 초기화 지연 의미 체계를 제공한다는 점을 제외하고는 Lazy<T>와 비슷합니다.Resembles Lazy<T> except that it provides lazy initialization semantics on a thread-local basis. 모든 스레드는 고유 값에 액세스할 수 있습니다.Every thread has access to its own unique value.
LazyInitializer 클래스의 오버헤드 없이 개체의 초기화 지연을 위한 고급 static(Visual Basic의 Shared) 메서드를 제공합니다.Provides advanced static (Shared in Visual Basic) methods for lazy initialization of objects without the overhead of a class.

기본 초기화 지연Basic Lazy Initialization

초기화 지연 형식(예: MyType)을 지정하려면 다음 예에 표시된 대로 Lazy<MyType>(Visual Basic에서 Lazy(Of MyType))을 사용하세요.To define a lazy-initialized type, for example, MyType, use Lazy<MyType> (Lazy(Of MyType) in Visual Basic), as shown in the following example. Lazy<T> 생성자에 대리자가 전달되지 않으면 값 속성에 처음 액세스할 때 Activator.CreateInstance을 사용하여 래핑된 형식이 생성됩니다.If no delegate is passed in the Lazy<T> constructor, the wrapped type is created by using Activator.CreateInstance when the value property is first accessed. 형식에 매개 변수가 없는 생성자가 없는 경우 런타임 예외가 throw 됩니다.If the type does not have a parameterless constructor, a run-time exception is thrown.

다음 예에서 Orders는 데이터베이스에서 검색된 Order 개체의 배열을 포함하는 클래스라고 가정합니다.In the following example, assume that Orders is a class that contains an array of Order objects retrieved from a database. Customer 개체에는 Orders의 인스턴스가 포함되어 있지만 사용자 작업에 따라 Orders 개체의 데이터가 필요하지 않을 수 있습니다.A Customer object contains an instance of Orders, but depending on user actions, the data from the Orders object might not be required.

// Initialize by using default Lazy<T> constructor. The
// Orders array itself is not created yet.
Lazy<Orders> _orders = new Lazy<Orders>();
' Initialize by using default Lazy<T> constructor. The 
'Orders array itself is not created yet.
Dim _orders As Lazy(Of Orders) = New Lazy(Of Orders)()

생성 시 래핑된 형식에서 특정 생성자 오버로드를 호출하는 Lazy<T> 생성자에서 대리자를 전달할 수도 있고, 다음 예에 표시된 대로 필수인 기타 초기화 단계를 수행할 수 있습니다.You can also pass a delegate in the Lazy<T> constructor that invokes a specific constructor overload on the wrapped type at creation time, and perform any other initialization steps that are required, as shown in the following example.

// Initialize by invoking a specific constructor on Order when Value
// property is accessed
Lazy<Orders> _orders = new Lazy<Orders>(() => new Orders(100));
' Initialize by invoking a specific constructor on Order 
' when Value property is accessed
Dim _orders As Lazy(Of Orders) = New Lazy(Of Orders)(Function() New Orders(100))

지연 개체를 만들고 나면 지연 변수의 Value 속성에 처음으로 액세스할 때까지 Orders의 인스턴스가 생성되지 않습니다.After the Lazy object is created, no instance of Orders is created until the Value property of the Lazy variable is accessed for the first time. 처음 액세스할 때 래핑된 형식이 생성되고 반환되며 나중에 액세스하는 데 사용하기 위해 저장됩니다.On first access, the wrapped type is created and returned, and stored for any future access.

// We need to create the array only if displayOrders is true
if (displayOrders == true)
{
    DisplayOrders(_orders.Value.OrderData);
}
else
{
    // Don't waste resources getting order data.
}
' We need to create the array only if _displayOrders is true
If _displayOrders = True Then
    DisplayOrders(_orders.Value.OrderData)
Else
    ' Don't waste resources getting order data.
End If

Lazy<T> 개체는 항상 초기화 시 사용된 값 또는 개체와 동일한 값 또는 개체를 반환합니다.A Lazy<T> object always returns the same object or value that it was initialized with. 따라서 Value 속성은 읽기 전용입니다.Therefore, the Value property is read-only. Value에서 참조 형식을 저장하면 새로운 개체를 할당할 수 없습니다.If Value stores a reference type, you cannot assign a new object to it. 그러나 설정할 수 있는 공용 필드 및 속성의 값을 변경할 수 있습니다. Value 에서 값 형식을 저장 하는 경우 해당 값을 수정할 수 없습니다.(However, you can change the value of its settable public fields and properties.) If Value stores a value type, you cannot modify its value. 그렇지만 새 인수를 사용하여 변수 생성자를 다시 호출하면 새 변수를 만들 수 있습니다.Nevertheless, you can create a new variable by invoking the variable constructor again by using new arguments.

_orders = new Lazy<Orders>(() => new Orders(10));
_orders = New Lazy(Of Orders)(Function() New Orders(10))

이전 지연 인스턴스와 같이 새로운 지연 인스턴스는 Value 속성에 처음 액세스할 때까지 Orders를 인스턴스화하지 않습니다.The new lazy instance, like the earlier one, does not instantiate Orders until its Value property is first accessed.

스레드로부터 안전한 초기화Thread-Safe Initialization

기본적으로 Lazy<T> 개체는 스레드로부터 안전합니다.By default, Lazy<T> objects are thread-safe. 즉, 생성자가 스레드 보안 유형을 지정하지 않으면 생성된 Lazy<T> 개체는 스레드로부터 안전합니다.That is, if the constructor does not specify the kind of thread safety, the Lazy<T> objects it creates are thread-safe. 다중 스레드 시나리오에서 스레드로부터 안전한 Lazy<T> 개체의 Value 속성에 액세스하는 첫 번째 스레드가 모든 스레드에서의 모든 후속 액세스를 위해 개체를 초기화하고 모든 스레드에서 동일한 데이터를 공유합니다.In multi-threaded scenarios, the first thread to access the Value property of a thread-safe Lazy<T> object initializes it for all subsequent accesses on all threads, and all threads share the same data. 따라서 어떤 스레드가 개체를 초기화하는지는 중요하지 않으며 경합 상태는 심각하지 않습니다.Therefore, it does not matter which thread initializes the object, and race conditions are benign.

참고

예외 캐싱을 사용하여 이러한 일관성을 오류 조건까지 확장할 수 있습니다.You can extend this consistency to error conditions by using exception caching. 자세한 내용은 다음 섹션인 Lazy 개체의 예외를 참조하세요.For more information, see the next section, Exceptions in Lazy Objects.

다음 예에서는 동일한 Lazy<int> 인스턴스에서는 개별 스레드의 값이 동일함을 보여 줍니다.The following example shows that the same Lazy<int> instance has the same value for three separate threads.

// Initialize the integer to the managed thread id of the
// first thread that accesses the Value property.
Lazy<int> number = new Lazy<int>(() => Thread.CurrentThread.ManagedThreadId);

Thread t1 = new Thread(() => Console.WriteLine("number on t1 = {0} ThreadID = {1}",
                                        number.Value, Thread.CurrentThread.ManagedThreadId));
t1.Start();

Thread t2 = new Thread(() => Console.WriteLine("number on t2 = {0} ThreadID = {1}",
                                        number.Value, Thread.CurrentThread.ManagedThreadId));
t2.Start();

Thread t3 = new Thread(() => Console.WriteLine("number on t3 = {0} ThreadID = {1}", number.Value,
                                        Thread.CurrentThread.ManagedThreadId));
t3.Start();

// Ensure that thread IDs are not recycled if the
// first thread completes before the last one starts.
t1.Join();
t2.Join();
t3.Join();

/* Sample Output:
    number on t1 = 11 ThreadID = 11
    number on t3 = 11 ThreadID = 13
    number on t2 = 11 ThreadID = 12
    Press any key to exit.
*/
' Initialize the integer to the managed thread id of the 
' first thread that accesses the Value property.
Dim number As Lazy(Of Integer) = New Lazy(Of Integer)(Function()
                                                          Return Thread.CurrentThread.ManagedThreadId
                                                      End Function)

Dim t1 As New Thread(Sub()
                         Console.WriteLine("number on t1 = {0} threadID = {1}",
                                           number.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t1.Start()

Dim t2 As New Thread(Sub()
                         Console.WriteLine("number on t2 = {0} threadID = {1}",
                                           number.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t2.Start()

Dim t3 As New Thread(Sub()
                         Console.WriteLine("number on t3 = {0} threadID = {1}",
                                           number.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t3.Start()

' Ensure that thread IDs are not recycled if the 
' first thread completes before the last one starts.
t1.Join()
t2.Join()
t3.Join()

' Sample Output:
'       number on t1 = 11 ThreadID = 11
'       number on t3 = 11 ThreadID = 13
'       number on t2 = 11 ThreadID = 12
'       Press any key to exit.

스레드마다 개별 데이터가 필요한 경우 이 항목의 뒷부분에 설명된 대로 ThreadLocal<T> 형식을 사용합니다.If you require separate data on each thread, use the ThreadLocal<T> type, as described later in this topic.

일부 Lazy<T> 생성자에는 여러 스레드에서 Value 속성에 액세스할지 지정하는 데 사용하는 isThreadSafe라는 부울 매개 변수가 있습니다.Some Lazy<T> constructors have a Boolean parameter named isThreadSafe that is used to specify whether the Value property will be accessed from multiple threads. 하나의 스레드에서만 속성에 액세스하게 하려는 경우 false를 전달하여 보통 수준의 성능 이점을 얻을 수 있습니다.If you intend to access the property from just one thread, pass in false to obtain a modest performance benefit. 여러 스레드에서 속성에 액세스하게 하려면 true를 전달하여 초기화 시 한 스레드에서 예외 처리를 하는 경합 상태를 올바르게 처리하도록 Lazy<T> 인스턴스에 지시합니다.If you intend to access the property from multiple threads, pass in true to instruct the Lazy<T> instance to correctly handle race conditions in which one thread throws an exception at initialization time.

일부 Lazy<T> 생성자에는 mode라는 LazyThreadSafetyMode 매개 변수가 있습니다.Some Lazy<T> constructors have a LazyThreadSafetyMode parameter named mode. 이러한 생성자는 추가 스레드 보안 모드를 제공합니다.These constructors provide an additional thread safety mode. 다음 표에서는 스레드 보안을 지정하는 생성자 매개 변수가 Lazy<T> 개체의 스레드 보안에 미치는 영향을 보여 줍니다.The following table shows how the thread safety of a Lazy<T> object is affected by constructor parameters that specify thread safety. 각 생성자에는 이러한 매개 변수가 최대 한 개 있습니다.Each constructor has at most one such parameter.

개체의 스레드 보안Thread safety of the object LazyThreadSafetyMode``mode매개 변수LazyThreadSafetyMode mode parameter 부울 isThreadSafe 매개 변수Boolean isThreadSafe parameter 스레드 보안 매개 변수 없음No thread safety parameters
완벽하게 스레드로부터 안전. 한 번에 하나의 스레드만 값을 초기화하려고 합니다.Fully thread-safe; only one thread at a time tries to initialize the value. ExecutionAndPublication true 예.Yes.
스레드로부터 안전하지 않음.Not thread-safe. None false 해당 사항 없음Not applicable.
완벽하게 스레드로부터 안전. 스레드에서 값을 초기화하기 위해 경합합니다.Fully thread-safe; threads race to initialize the value. PublicationOnly 해당 사항 없음Not applicable. 해당 사항 없음Not applicable.

표에 표시된 바와 같이 mode 매개 변수에 대해 LazyThreadSafetyMode.ExecutionAndPublication을 지정하면 isThreadSafe 매개 변수에 대해 true를 지정하는 것과 같으며 LazyThreadSafetyMode.None을 지정하면 false를 지정하는 것과 같습니다.As the table shows, specifying LazyThreadSafetyMode.ExecutionAndPublication for the mode parameter is the same as specifying true for the isThreadSafe parameter, and specifying LazyThreadSafetyMode.None is the same as specifying false.

LazyThreadSafetyMode.PublicationOnly을 지정하면 여러 스레드에서 Lazy<T> 인스턴스를 초기화하려고 시도할 수 있습니다.Specifying LazyThreadSafetyMode.PublicationOnly allows multiple threads to attempt to initialize the Lazy<T> instance. 하나의 스레드만 이 경합에서 이길 수 있고 다른 모든 스레드는 성공한 스레드를 통해 초기화된 값을 받습니다.Only one thread can win this race, and all the other threads receive the value that was initialized by the successful thread. 초기화 중에 스레드에서 예외가 throw되면 해당 스레드는 성공한 스레드를 통해 설정된 값을 받지 못합니다.If an exception is thrown on a thread during initialization, that thread does not receive the value set by the successful thread. 예외가 캐시되지 않으므로 다음에 Value 속성에 액세스하려고 하면 초기화할 수 있습니다.Exceptions are not cached, so a subsequent attempt to access the Value property can result in successful initialization. 이 방식은 다음 섹션에 설명된 대로 다른 모드에서 예외를 처리하는 방식과는 다릅니다.This differs from the way exceptions are treated in other modes, which is described in the following section. 자세한 내용은 LazyThreadSafetyMode 열거형을 참조하세요.For more information, see the LazyThreadSafetyMode enumeration.

Lazy 개체의 예외Exceptions in Lazy Objects

앞에서 설명한 것처럼 Lazy<T> 개체는 항상 초기화 시와 동일한 개체 또는 값을 반환하므로 Value 속성은 읽기 전용입니다.As stated earlier, a Lazy<T> object always returns the same object or value that it was initialized with, and therefore the Value property is read-only. 예외 캐싱을 사용하도록 설정하면 이 불변성이 예외 동작까지 확장됩니다.If you enable exception caching, this immutability also extends to exception behavior. 지연 초기화 된 개체에 예외 캐싱이 설정 되어 있고 속성에 처음 액세스할 때 초기화 메서드에서 예외를 throw 하는 경우 Value 모든 후속 시도에서 속성에 대 한 액세스를 시도할 때마다 동일한 예외가 throw 됩니다 Value .If a lazy-initialized object has exception caching enabled and throws an exception from its initialization method when the Value property is first accessed, that same exception is thrown on every subsequent attempt to access the Value property. 즉, 다중 스레드 시나리오에서도 래핑된 형식의 생성자가 다시 호출되지 않습니다.In other words, the constructor of the wrapped type is never re-invoked, even in multithreaded scenarios. 따라서 Lazy<T> 개체는 한 번의 액세스에서 예외 처리를 할 수 없으며 후속 액세스에서 값을 반환할 수 없습니다.Therefore, the Lazy<T> object cannot throw an exception on one access and return a value on a subsequent access.

초기화 메서드(valueFactory 매개 변수)를 사용하는 System.Lazy<T> 생성자를 사용할 때 예외 캐싱이 사용됩니다. 예를 들어 Lazy(T)(Func(T)) 생성자를 사용할 때 사용됩니다.Exception caching is enabled when you use any System.Lazy<T> constructor that takes an initialization method (valueFactory parameter); for example, it is enabled when you use the Lazy(T)(Func(T))constructor. 생성자에서 LazyThreadSafetyMode 값(mode 매개 변수)도 사용하는 경우 LazyThreadSafetyMode.ExecutionAndPublication 또는 LazyThreadSafetyMode.None을 지정하세요.If the constructor also takes a LazyThreadSafetyMode value (mode parameter), specify LazyThreadSafetyMode.ExecutionAndPublication or LazyThreadSafetyMode.None. 초기화 메서드를 지정하면 이 두 모드에 대해 예외 캐싱을 사용합니다.Specifying an initialization method enables exception caching for these two modes. 초기화 메서드는 매우 간단할 수 있습니다.The initialization method can be very simple. 예를 들어 T new Lazy<Contents>(() => new Contents(), mode) c #의 경우 또는 New Lazy(Of Contents)(Function() New Contents()) Visual Basic에서 매개 변수가 없는 생성자를 호출할 수 있습니다.For example, it might call the parameterless constructor for T: new Lazy<Contents>(() => new Contents(), mode) in C#, or New Lazy(Of Contents)(Function() New Contents()) in Visual Basic. 초기화 메소드를 지정하지 않는 System.Lazy<T> 생성자를 사용하는 경우 T의 매개 변수가 없는 생성자가 throw하는 예외는 캐싱되지 않습니다.If you use a System.Lazy<T> constructor that does not specify an initialization method, exceptions that are thrown by the parameterless constructor for T are not cached. 자세한 내용은 LazyThreadSafetyMode 열거형을 참조하세요.For more information, see the LazyThreadSafetyMode enumeration.

참고

isThreadSafe 생성자 매개 변수를 false로 설정하거나 mode 생성자 매개 변수를 LazyThreadSafetyMode.None으로 설정하여 Lazy<T> 개체를 만들면 단일 스레드에서 Lazy<T> 개체에 액세스하거나 고유 동기화를 제공해야 합니다.If you create a Lazy<T> object with the isThreadSafe constructor parameter set to false or the mode constructor parameter set to LazyThreadSafetyMode.None, you must access the Lazy<T> object from a single thread or provide your own synchronization. 그러면 예외 캐싱을 포함하여 개체의 모든 요소에 적용됩니다.This applies to all aspects of the object, including exception caching.

이전 섹션에서 설명한 대로 LazyThreadSafetyMode.PublicationOnly을 지정하여 만든 Lazy<T> 개체는 예외를 다르게 처리합니다.As noted in the previous section, Lazy<T> objects created by specifying LazyThreadSafetyMode.PublicationOnly treat exceptions differently. PublicationOnly를 사용하면 여러 스레드에서 Lazy<T> 인스턴스를 초기화하기 위해 경쟁할 수 있습니다.With PublicationOnly, multiple threads can compete to initialize the Lazy<T> instance. 이 경우 예외가 캐시되지 않고, 초기화에 성공할 때까지 Value 속성에 계속 액세스하려고 시도할 수 있습니다.In this case, exceptions are not cached, and attempts to access the Value property can continue until initialization is successful.

다음 표에는 Lazy<T> 생성자가 예외 캐싱을 제어하는 방식이 요약되어 있습니다.The following table summarizes the way the Lazy<T> constructors control exception caching.

생성자Constructor 스레드 보안 모드Thread safety mode 초기화 메서드 사용Uses initialization method 예외가 캐시됨Exceptions are cached
Lazy(T)()Lazy(T)() (ExecutionAndPublication)(ExecutionAndPublication) 아니요No 아니요No
Lazy(T)(Func(T))Lazy(T)(Func(T)) (ExecutionAndPublication)(ExecutionAndPublication) Yes Yes
Lazy(T)(Boolean)Lazy(T)(Boolean) True(ExecutionAndPublication) 또는 false(None)True (ExecutionAndPublication) or false (None) 아니요No 아니요No
Lazy(T)(Func(T), Boolean)Lazy(T)(Func(T), Boolean) True(ExecutionAndPublication) 또는 false(None)True (ExecutionAndPublication) or false (None) Yes Yes
Lazy(T)(LazyThreadSafetyMode)Lazy(T)(LazyThreadSafetyMode) 사용자 지정User-specified 아니요No 아니요No
Lazy(T)(Func(T), LazyThreadSafetyMode)Lazy(T)(Func(T), LazyThreadSafetyMode) 사용자 지정User-specified Yes 사용자가 PublicationOnly를 지정하는 경우 No, 지정하지 않으면 Yes.No if user specifies PublicationOnly; otherwise, yes.

초기화 지연 속성 구현Implementing a Lazy-Initialized Property

초기화 지연을 사용하여 공용 속성을 구현하려면 속성의 지원 필드를 Lazy<T>로 정의하고 속성의 get 접근자에서 Value 속성을 반환합니다.To implement a public property by using lazy initialization, define the backing field of the property as a Lazy<T>, and return the Value property from the get accessor of the property.

class Customer
{
    private Lazy<Orders> _orders;
    public string CustomerID {get; private set;}
    public Customer(string id)
    {
        CustomerID = id;
        _orders = new Lazy<Orders>(() =>
        {
            // You can specify any additional
            // initialization steps here.
            return new Orders(this.CustomerID);
        });
    }

    public Orders MyOrders
    {
        get
        {
            // Orders is created on first access here.
            return _orders.Value;
        }
    }
}
Class Customer
    Private _orders As Lazy(Of Orders)
    Public Shared CustomerID As String
    Public Sub New(ByVal id As String)
        CustomerID = id
        _orders = New Lazy(Of Orders)(Function()
                                          ' You can specify additional 
                                          ' initialization steps here
                                          Return New Orders(CustomerID)
                                      End Function)

    End Sub
    Public ReadOnly Property MyOrders As Orders

        Get
            Return _orders.Value
        End Get

    End Property

End Class

Value 속성은 읽기 전용입니다. 따라서 이 속성을 노출하는 속성에는 set 접근자가 없습니다.The Value property is read-only; therefore, the property that exposes it has no set accessor. Lazy<T> 개체에서 지원하는 읽기/쓰기 속성이 필요하면 set 접근자가 새로운 Lazy<T> 개체를 만들어 백업 저장소에 할당해야 합니다.If you require a read/write property backed by a Lazy<T> object, the set accessor must create a new Lazy<T> object and assign it to the backing store. set 접근자는 set 접근자에 전달된 새 속성 값을 반환하는 람다 식을 생성해야 하며 이 람다 식을 새로운 Lazy<T> 개체의 생성자에 전달해야 합니다.The set accessor must create a lambda expression that returns the new property value that was passed to the set accessor, and pass that lambda expression to the constructor for the new Lazy<T> object. 다음번에 Value 속성에 액세스하면 새로운 Lazy<T>가 초기화되고, 그 이후로 Value 속성이 속성에 할당된 새 값을 반환합니다.The next access of the Value property will cause initialization of the new Lazy<T>, and its Value property will thereafter return the new value that was assigned to the property. 이와 같이 복잡하게 배열하는 이유는 Lazy<T>에 기본 제공된 다중 스레딩 보호를 유지하기 위해서입니다.The reason for this convoluted arrangement is to preserve the multithreading protections built into Lazy<T>. 그러지 않으면 속성 접근자가 Value 속성을 통해 반환된 첫 번째 값을 캐시하고 캐시된 값만 수정해야 하며, 이 작업을 수행하려면 스레드로부터 안전한 코드를 고유하게 작성해야 합니다.Otherwise, the property accessors would have to cache the first value returned by the Value property and only modify the cached value, and you would have to write your own thread-safe code to do that. Lazy<T> 개체에서 지원하는 읽기/쓰기 속성에 필요한 추가 초기화때문에 성능이 문제가 될 수 있습니다.Because of the additional initializations required by a read/write property backed by a Lazy<T> object, the performance might not be acceptable. 또한 특정 시나리오에 따라 setter와 getter 사이의 경합 상태를 방지하기 위해 추가 조정이 필요할 수 있습니다.Furthermore, depending on the specific scenario, additional coordination might be required to avoid race conditions between setters and getters.

스레드 로컬 초기화 지연Thread-Local Lazy Initialization

일부 다중 스레드 시나리오에서는 각 스레드에 고유 개인 데이터를 제공할 수 있습니다.In some multithreaded scenarios, you might want to give each thread its own private data. 이러한 데이터는 스레드 로컬 데이터 라고 합니다.Such data is called thread-local data. .NET Framework 버전 3.5 이하에서 ThreadStatic 특성을 정적 변수에 적용하여 스레드 로컬로 만들 수 있습니다.In the .NET Framework version 3.5 and earlier, you could apply the ThreadStatic attribute to a static variable to make it thread-local. 그러나 ThreadStatic 특성을 사용하면 사소한 오류가 발생할 수 있습니다.However, using the ThreadStatic attribute can lead to subtle errors. 예를 들어 다음 예제에 표시된 대로 기본 초기화 명령문도 액세스하는 첫 번째 스레드에서만 변수가 초기화되게 합니다.For example, even basic initialization statements will cause the variable to be initialized only on the first thread that accesses it, as shown in the following example.

[ThreadStatic]
static int counter = 1;
<ThreadStatic()>
Shared counter As Integer

다른 모든 스레드에서는 변수가 기본값(0)을 사용하여 초기화됩니다.On all other threads, the variable will be initialized by using its default value (zero). .NET Framework 버전 4의 대안으로 System.Threading.ThreadLocal<T> 유형을 사용하여 인스턴스 기반의 스레드 지역 변수를 만들 수 있습니다. 이 변수는 사용자가 제공하는 Action<T> 대리자가 모든 스레드에서 초기화됩니다.As an alternative in the .NET Framework version 4, you can use the System.Threading.ThreadLocal<T> type to create an instance-based, thread-local variable that is initialized on all threads by the Action<T> delegate that you provide. 다음 예제에서 counter에 액세스하는 모든 스레드에는 시작 값이 1로 표시됩니다.In the following example, all threads that access counter will see its starting value as 1.

ThreadLocal<int> betterCounter = new ThreadLocal<int>(() => 1);
Dim betterCounter As ThreadLocal(Of Integer) = New ThreadLocal(Of Integer)(Function() 1)

ThreadLocal<T>에서는 Lazy<T>와 거의 동일한 방식으로 개체를 래핑합니다. 단, 다음과 같은 중요한 차이점이 있습니다.ThreadLocal<T> wraps its object in much the same way as Lazy<T>, with these essential differences:

  • 각 스레드가 다른 스레드에서 액세스할 수 없는 고유한 개인 데이터를 사용하여 스레드 지역 변수를 초기화합니다.Each thread initializes the thread-local variable by using its own private data that is not accessible from other threads.

  • ThreadLocal<T>.Value 속성은 읽기-쓰기이고 여러 번 수정할 수 있습니다.The ThreadLocal<T>.Value property is read-write, and can be modified any number of times. 따라서 예외 전파에 영향을 미칠 수 있습니다. 예를 들어 하나의 get 작업에서는 예외가 throw될 수 있지만, 다른 작업에서는 성공적으로 값을 초기화할 수 있습니다.This can affect exception propagation, for example, one get operation can raise an exception but the next one can successfully initialize the value.

  • 초기화 대리자가 제공되지 않으면 ThreadLocal<T>에서 형식의 기본값을 사용하여 래핑된 형식을 초기화합니다.If no initialization delegate is provided, ThreadLocal<T> will initialize its wrapped type by using the default value of the type. 이런 점에서 ThreadLocal<T>ThreadStaticAttribute 특성과 일치합니다.In this regard, ThreadLocal<T> is consistent with the ThreadStaticAttribute attribute.

다음 예제에서는 ThreadLocal<int> 인스턴스에 액세스하는 모든 스레드가 고유한 데이터 복사본을 가져옵니다.The following example demonstrates that every thread that accesses the ThreadLocal<int> instance gets its own unique copy of the data.

// Initialize the integer to the managed thread id on a per-thread basis.
ThreadLocal<int> threadLocalNumber = new ThreadLocal<int>(() => Thread.CurrentThread.ManagedThreadId);
Thread t4 = new Thread(() => Console.WriteLine("threadLocalNumber on t4 = {0} ThreadID = {1}",
                                    threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t4.Start();

Thread t5 = new Thread(() => Console.WriteLine("threadLocalNumber on t5 = {0} ThreadID = {1}",
                                    threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t5.Start();

Thread t6 = new Thread(() => Console.WriteLine("threadLocalNumber on t6 = {0} ThreadID = {1}",
                                    threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t6.Start();

// Ensure that thread IDs are not recycled if the
// first thread completes before the last one starts.
t4.Join();
t5.Join();
t6.Join();

/* Sample Output:
   threadLocalNumber on t4 = 14 ThreadID = 14
   threadLocalNumber on t5 = 15 ThreadID = 15
   threadLocalNumber on t6 = 16 ThreadID = 16
*/
' Initialize the integer to the managed thread id on a per-thread basis.
Dim threadLocalNumber As New ThreadLocal(Of Integer)(Function() Thread.CurrentThread.ManagedThreadId)
Dim t4 As New Thread(Sub()
                         Console.WriteLine("number on t4 = {0} threadID = {1}",
                                           threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t4.Start()

Dim t5 As New Thread(Sub()
                         Console.WriteLine("number on t5 = {0} threadID = {1}",
                                           threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t5.Start()

Dim t6 As New Thread(Sub()
                         Console.WriteLine("number on t6 = {0} threadID = {1}",
                                           threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t6.Start()

' Ensure that thread IDs are not recycled if the 
' first thread completes before the last one starts.
t4.Join()
t5.Join()
t6.Join()

'Sample(Output)
'      threadLocalNumber on t4 = 14 ThreadID = 14 
'      threadLocalNumber on t5 = 15 ThreadID = 15
'      threadLocalNumber on t6 = 16 ThreadID = 16 

Parallel.For 및 ForEach의 스레드 지역 변수Thread-Local Variables in Parallel.For and ForEach

Parallel.For 메서드 또는 Parallel.ForEach 메서드를 사용하여 데이터 소스를 병렬로 반복할 때 스레드 로컬 데이터 지원을 기본으로 제공하는 오버로드를 사용할 수 있습니다.When you use the Parallel.For method or Parallel.ForEach method to iterate over data sources in parallel, you can use the overloads that have built-in support for thread-local data. 이러한 메서드에서는 데이터를 만들고 액세스하며 정리하기 위해 로컬 대리자를 사용하여 스레드 국부성을 달성합니다.In these methods, the thread-locality is achieved by using local delegates to create, access, and clean up the data. 자세한 내용은 방법: 스레드 로컬 변수를 사용하는 Parallel.For 루프 작성방법: 파티션 로컬 변수를 사용하는 Parallel.ForEach 루프 작성을 참조하세요.For more information, see How to: Write a Parallel.For Loop with Thread-Local Variables and How to: Write a Parallel.ForEach Loop with Partition-Local Variables.

오버헤드가 적은 시나리오에 초기화 지연 사용Using Lazy Initialization for Low-Overhead Scenarios

다수의 개체를 초기화 지연해야 하는 시나리오에서는 Lazy<T>의 각 개체를 래핑하는 데 너무 많은 메모리 또는 너무 많은 계산 리소스가 필요한지 판별할 수 있습니다.In scenarios where you have to lazy-initialize a large number of objects, you might decide that wrapping each object in a Lazy<T> requires too much memory or too many computing resources. 또는 초기화 지연 노출 방법에 대한 엄격한 요구 사항이 있을 수 있습니다.Or, you might have stringent requirements about how lazy initialization is exposed. 이 경우 System.Threading.LazyInitializer 클래스의 static(Visual Basic에서 Shared) 메서드를 사용하여 Lazy<T> 인스턴스에 래핑하지 않고 각 개체의 초기화를 지연할 수 있습니다.In such cases, you can use the static (Shared in Visual Basic) methods of the System.Threading.LazyInitializer class to lazy-initialize each object without wrapping it in an instance of Lazy<T>.

다음 예에서는 하나의 Lazy<T> 개체에 전체 Orders 개체를 래핑하지 않고 필요한 경우에만 개별 Order 개체의 초기화를 지연합니다.In the following example, assume that, instead of wrapping an entire Orders object in one Lazy<T> object, you have lazy-initialized individual Order objects only if they are required.

// Assume that _orders contains null values, and
// we only need to initialize them if displayOrderInfo is true
if (displayOrderInfo == true)
{
    for (int i = 0; i < _orders.Length; i++)
    {
        // Lazily initialize the orders without wrapping them in a Lazy<T>
        LazyInitializer.EnsureInitialized(ref _orders[i], () =>
        {
            // Returns the value that will be placed in the ref parameter.
            return GetOrderForIndex(i);
        });
    }
}
' Assume that _orders contains null values, and
' we only need to initialize them if displayOrderInfo is true
If displayOrderInfo = True Then


    For i As Integer = 0 To _orders.Length
        ' Lazily initialize the orders without wrapping them in a Lazy(Of T)
        LazyInitializer.EnsureInitialized(_orders(i), Function()
                                                          ' Returns the value that will be placed in the ref parameter.
                                                          Return GetOrderForIndex(i)
                                                      End Function)
    Next
End If

이 예에서는 루프를 반복할 때마다 초기화 프로시저가 호출됩니다.In this example, notice that the initialization procedure is invoked on every iteration of the loop. 다중 스레드 시나리오에서 초기화 프로시저를 호출하는 첫 번째 스레드는 모든 스레드에 해당 값이 표시되는 스레드입니다.In multi-threaded scenarios, the first thread to invoke the initialization procedure is the one whose value is seen by all threads. 나중에 스레드에서 초기화 프로시저도 호출하지만 해당 결과는 사용하지 않습니다.Later threads also invoke the initialization procedure, but their results are not used. 이 유형의 잠재적 경합 상태가 허용되지 않는 경우 부울 인수와 동기화 개체를 사용하는 LazyInitializer.EnsureInitialized의 오버로드를 사용합니다.If this kind of potential race condition is not acceptable, use the overload of LazyInitializer.EnsureInitialized that takes a Boolean argument and a synchronization object.

참조See also