초기화 지연

개체 초기화 지연은 개체를 처음 사용할 때까지 생성이 지연된다는 의미입니다. (이 토픽에서 초기화 지연인스턴스화 지연이라는 용어는 동의어입니다.) 초기화 지연은 주로 성능을 개선하고 불필요한 계산을 피하며 프로그램 메모리 요구 사항을 줄이는 데 사용됩니다. 다음은 가장 일반적인 시나리오입니다.

  • 만드는 데 비용이 많이 드는 개체가 있으며 프로그램에서 이 개체를 사용하지 않는 경우가 있습니다. 예를 들어 초기화되기 위해 데이터베이스 연결이 필요한 큰 Order 개체 배열이 포함된 Orders 속성이 있는 Customer 개체가 메모리에 있다고 가정합니다. 사용자는 주문을 표시하거나 계산에서 데이터를 사용하도록 요청하지 않으면 시스템 메모리 또는 계산 주기를 사용하여 만들 이유가 없습니다. Lazy<Orders>를 사용하여 Orders 개체의 초기화 지연을 선언하면 개체를 사용하지 않을 때 시스템 리소스 낭비를 방지할 수 있습니다.

  • 만드는 데 비용이 많이 드는 개체가 있고 다른 비용이 많이 드는 작업이 완료될 때까지 생성을 지연시키려는 경우가 있습니다. 예를 들어, 프로그램을 시작할 때 여러 개체 인스턴스를 로드하지만 그중 일부만 즉시 필요합니다. 필요한 개체를 만들 때까지 필요하지 않은 개체의 초기화를 지연하여 프로그램의 시작 성능을 향상시킬 수 있습니다.

초기화 지연을 수행하도록 고유 코드를 작성할 수 있지만 Lazy<T>를 대신 사용하는 것이 좋습니다. Lazy<T> 및 관련 유형에서도 스레드로부터 안전을 지원하고 일관된 예외 전파 정책을 제공합니다.

다음 표에는 다양한 시나리오에서 초기화 지연을 사용하도록 .NET Framework 버전 4에서 제공하는 유형이 나열되어 있습니다.

Type 설명
Lazy<T> 클래스 라이브러리 또는 사용자 정의 형식에 대한 초기화 지연 의미 체계를 제공하는 래퍼 클래스입니다.
ThreadLocal<T> 스레드-로컬 기반으로 초기화 지연 의미 체계를 제공한다는 점을 제외하고는 Lazy<T>와 비슷합니다. 모든 스레드는 고유 값에 액세스할 수 있습니다.
LazyInitializer 클래스의 오버헤드 없이 개체의 초기화 지연을 위한 고급 static(Visual Basic의 Shared) 메서드를 제공합니다.

기본 초기화 지연

초기화 지연 형식(예: MyType)을 지정하려면 다음 예에 표시된 대로 Lazy<MyType>(Visual Basic에서 Lazy(Of MyType))을 사용하세요. Lazy<T> 생성자에 대리자가 전달되지 않으면 값 속성에 처음 액세스할 때 Activator.CreateInstance을 사용하여 래핑된 형식이 생성됩니다. 형식에 매개 변수가 없는 생성자가 없는 경우 런타임 예외가 throw됩니다.

다음 예에서 Orders는 데이터베이스에서 검색된 Order 개체의 배열을 포함하는 클래스라고 가정합니다. Customer 개체에는 Orders의 인스턴스가 포함되어 있지만 사용자 작업에 따라 Orders 개체의 데이터가 필요하지 않을 수 있습니다.

// 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> 생성자에서 대리자를 전달할 수도 있고, 다음 예에 표시된 대로 필수인 기타 초기화 단계를 수행할 수 있습니다.

// 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의 인스턴스가 생성되지 않습니다. 처음 액세스할 때 래핑된 형식이 생성되고 반환되며 나중에 액세스하는 데 사용하기 위해 저장됩니다.

// 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> 개체는 항상 초기화 시 사용된 값 또는 개체와 동일한 값 또는 개체를 반환합니다. 따라서 Value 속성은 읽기 전용입니다. Value에서 참조 형식을 저장하면 새로운 개체를 할당할 수 없습니다. (그러나 설정 가능한 공개 필드 및 속성의 값은 변경할 수 있습니다.) Value이 값 형식을 저장하는 경우 해당 값을 수정할 수 없습니다. 그렇지만 새 인수를 사용하여 변수 생성자를 다시 호출하면 새 변수를 만들 수 있습니다.

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

이전 지연 인스턴스와 같이 새로운 지연 인스턴스는 Value 속성에 처음 액세스할 때까지 Orders를 인스턴스화하지 않습니다.

스레드로부터 안전한 초기화

기본적으로 Lazy<T> 개체는 스레드로부터 안전합니다. 즉, 생성자가 스레드 보안 유형을 지정하지 않으면 생성된 Lazy<T> 개체는 스레드로부터 안전합니다. 다중 스레드 시나리오에서 스레드로부터 안전한 Lazy<T> 개체의 Value 속성에 액세스하는 첫 번째 스레드가 모든 스레드에서의 모든 후속 액세스를 위해 개체를 초기화하고 모든 스레드에서 동일한 데이터를 공유합니다. 따라서 어떤 스레드가 개체를 초기화하는지는 중요하지 않으며 경합 상태는 심각하지 않습니다.

참고 항목

예외 캐싱을 사용하여 이러한 일관성을 오류 조건까지 확장할 수 있습니다. 자세한 내용은 다음 섹션인 Lazy 개체의 예외를 참조하세요.

다음 예에서는 동일한 Lazy<int> 인스턴스에서는 개별 스레드의 값이 동일함을 보여 줍니다.

// 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> 형식을 사용합니다.

일부 Lazy<T> 생성자에는 여러 스레드에서 Value 속성에 액세스할지 지정하는 데 사용하는 isThreadSafe라는 부울 매개 변수가 있습니다. 하나의 스레드에서만 속성에 액세스하게 하려는 경우 false를 전달하여 보통 수준의 성능 이점을 얻을 수 있습니다. 여러 스레드에서 속성에 액세스하게 하려면 true를 전달하여 초기화 시 한 스레드에서 예외 처리를 하는 경합 상태를 올바르게 처리하도록 Lazy<T> 인스턴스에 지시합니다.

일부 Lazy<T> 생성자에는 mode라는 LazyThreadSafetyMode 매개 변수가 있습니다. 이러한 생성자는 추가 스레드 보안 모드를 제공합니다. 다음 표에서는 스레드 보안을 지정하는 생성자 매개 변수가 Lazy<T> 개체의 스레드 보안에 미치는 영향을 보여 줍니다. 각 생성자에는 이러한 매개 변수가 최대 한 개 있습니다.

개체의 스레드 보안 LazyThreadSafetyModemode 매개 변수 부울 isThreadSafe 매개 변수 스레드 보안 매개 변수 없음
완벽하게 스레드로부터 안전. 한 번에 하나의 스레드만 값을 초기화하려고 합니다. ExecutionAndPublication true 예.
스레드로부터 안전하지 않음. None false 해당 없음.
완벽하게 스레드로부터 안전. 스레드에서 값을 초기화하기 위해 경합합니다. PublicationOnly 해당 사항 없음 해당 사항 없음

표에 표시된 바와 같이 mode 매개 변수에 대해 LazyThreadSafetyMode.ExecutionAndPublication을 지정하면 isThreadSafe 매개 변수에 대해 true를 지정하는 것과 같으며 LazyThreadSafetyMode.None을 지정하면 false를 지정하는 것과 같습니다.

ExecutionPublication가 참조하는 내용에 대한 자세한 내용은 LazyThreadSafetyMode를 참조하세요.

LazyThreadSafetyMode.PublicationOnly을 지정하면 여러 스레드에서 Lazy<T> 인스턴스를 초기화하려고 시도할 수 있습니다. 하나의 스레드만 이 경합에서 이길 수 있고 다른 모든 스레드는 성공한 스레드를 통해 초기화된 값을 받습니다. 초기화 중에 스레드에서 예외가 throw되면 해당 스레드는 성공한 스레드를 통해 설정된 값을 받지 못합니다. 예외가 캐시되지 않으므로 다음에 Value 속성에 액세스하려고 하면 초기화할 수 있습니다. 이 방식은 다음 섹션에 설명된 대로 다른 모드에서 예외를 처리하는 방식과는 다릅니다. 자세한 내용은 LazyThreadSafetyMode 열거형을 참조하세요.

Lazy 개체의 예외

앞에서 설명한 것처럼 Lazy<T> 개체는 항상 초기화 시와 동일한 개체 또는 값을 반환하므로 Value 속성은 읽기 전용입니다. 예외 캐싱을 사용하도록 설정하면 이 불변성이 예외 동작까지 확장됩니다. 초기화가 지연된 개체에 예외 캐싱이 사용되고 Value 속성에 처음 액세스할 때 초기화 메서드에서 예외가 throw되면 나중에 Value 속성에 액세스하려고 할 때마다 동일한 예외가 throw됩니다. 즉, 다중 스레드 시나리오에서도 래핑된 형식의 생성자가 다시 호출되지 않습니다. 따라서 Lazy<T> 개체는 한 번의 액세스에서 예외 처리를 할 수 없으며 후속 액세스에서 값을 반환할 수 없습니다.

초기화 메서드(valueFactory 매개 변수)를 사용하는 System.Lazy<T> 생성자를 사용할 때 예외 캐싱이 사용됩니다. 예를 들어 Lazy(T)(Func(T)) 생성자를 사용할 때 사용됩니다. 생성자에서 LazyThreadSafetyMode 값(mode 매개 변수)도 사용하는 경우 LazyThreadSafetyMode.ExecutionAndPublication 또는 LazyThreadSafetyMode.None을 지정하세요. 초기화 메서드를 지정하면 이 두 모드에 대해 예외 캐싱을 사용합니다. 초기화 메서드는 매우 간단할 수 있습니다. 예를 들어 T의 매개 변수가 없는 생성자를 호출할 수 있습니다. C#의 경우 new Lazy<Contents>(() => new Contents(), mode) 또는 Visual Basic의 경우 New Lazy(Of Contents)(Function() New Contents())입니다. 초기화 메소드를 지정하지 않는 System.Lazy<T> 생성자를 사용하는 경우 T의 매개 변수가 없는 생성자가 throw하는 예외는 캐싱되지 않습니다. 자세한 내용은 LazyThreadSafetyMode 열거형을 참조하세요.

참고 항목

isThreadSafe 생성자 매개 변수를 false로 설정하거나 mode 생성자 매개 변수를 LazyThreadSafetyMode.None으로 설정하여 Lazy<T> 개체를 만들면 단일 스레드에서 Lazy<T> 개체에 액세스하거나 고유 동기화를 제공해야 합니다. 그러면 예외 캐싱을 포함하여 개체의 모든 요소에 적용됩니다.

이전 섹션에서 설명한 대로 LazyThreadSafetyMode.PublicationOnly을 지정하여 만든 Lazy<T> 개체는 예외를 다르게 처리합니다. PublicationOnly를 사용하면 여러 스레드에서 Lazy<T> 인스턴스를 초기화하기 위해 경쟁할 수 있습니다. 이 경우 예외가 캐시되지 않고, 초기화에 성공할 때까지 Value 속성에 계속 액세스하려고 시도할 수 있습니다.

다음 표에는 Lazy<T> 생성자가 예외 캐싱을 제어하는 방식이 요약되어 있습니다.

생성자 스레드 보안 모드 초기화 메서드 사용 예외가 캐시됨
Lazy(T)() (ExecutionAndPublication) 아니요 아니요
Lazy(T)(Func(T)) (ExecutionAndPublication)
Lazy(T)(Boolean) True(ExecutionAndPublication) 또는 false(None) 아니요 아니요
Lazy(T)(Func(T), Boolean) True(ExecutionAndPublication) 또는 false(None)
Lazy(T)(LazyThreadSafetyMode) 사용자 지정 아니요 아니요
Lazy(T)(Func(T), LazyThreadSafetyMode) 사용자 지정 사용자가 PublicationOnly를 지정하는 경우 No, 지정하지 않으면 Yes.

초기화 지연 속성 구현

초기화 지연을 사용하여 공용 속성을 구현하려면 속성의 지원 필드를 Lazy<T>로 정의하고 속성의 get 접근자에서 Value 속성을 반환합니다.

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 접근자가 없습니다. Lazy<T> 개체에서 지원하는 읽기/쓰기 속성이 필요하면 set 접근자가 새로운 Lazy<T> 개체를 만들어 백업 저장소에 할당해야 합니다. set 접근자는 set 접근자에 전달된 새 속성 값을 반환하는 람다 식을 생성해야 하며 이 람다 식을 새로운 Lazy<T> 개체의 생성자에 전달해야 합니다. 다음번에 Value 속성에 액세스하면 새로운 Lazy<T>가 초기화되고, 그 이후로 Value 속성이 속성에 할당된 새 값을 반환합니다. 이와 같이 복잡하게 배열하는 이유는 Lazy<T>에 기본 제공된 다중 스레딩 보호를 유지하기 위해서입니다. 그러지 않으면 속성 접근자가 Value 속성을 통해 반환된 첫 번째 값을 캐시하고 캐시된 값만 수정해야 하며, 이 작업을 수행하려면 스레드로부터 안전한 코드를 고유하게 작성해야 합니다. Lazy<T> 개체에서 지원하는 읽기/쓰기 속성에 필요한 추가 초기화때문에 성능이 문제가 될 수 있습니다. 또한 특정 시나리오에 따라 setter와 getter 사이의 경합 상태를 방지하기 위해 추가 조정이 필요할 수 있습니다.

스레드 로컬 초기화 지연

일부 다중 스레드 시나리오에서는 각 스레드에 고유 개인 데이터를 제공할 수 있습니다. 이러한 데이터는 스레드 로컬 데이터라고 합니다. .NET Framework 버전 3.5 이하에서 ThreadStatic 특성을 정적 변수에 적용하여 스레드 로컬로 만들 수 있습니다. 그러나 ThreadStatic 특성을 사용하면 사소한 오류가 발생할 수 있습니다. 예를 들어 다음 예제에 표시된 대로 기본 초기화 명령문도 액세스하는 첫 번째 스레드에서만 변수가 초기화되게 합니다.

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

다른 모든 스레드에서는 변수가 기본값(0)을 사용하여 초기화됩니다. .NET Framework 버전 4의 대안으로 System.Threading.ThreadLocal<T> 유형을 사용하여 인스턴스 기반의 스레드 지역 변수를 만들 수 있습니다. 이 변수는 사용자가 제공하는 Action<T> 대리자가 모든 스레드에서 초기화됩니다. 다음 예제에서 counter에 액세스하는 모든 스레드에는 시작 값이 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>.Value 속성은 읽기-쓰기이고 여러 번 수정할 수 있습니다. 따라서 예외 전파에 영향을 미칠 수 있습니다. 예를 들어 하나의 get 작업에서는 예외가 throw될 수 있지만, 다른 작업에서는 성공적으로 값을 초기화할 수 있습니다.

  • 초기화 대리자가 제공되지 않으면 ThreadLocal<T>에서 형식의 기본값을 사용하여 래핑된 형식을 초기화합니다. 이런 점에서 ThreadLocal<T>ThreadStaticAttribute 특성과 일치합니다.

다음 예제에서는 ThreadLocal<int> 인스턴스에 액세스하는 모든 스레드가 고유한 데이터 복사본을 가져옵니다.

// 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의 스레드 지역 변수

Parallel.For 메서드 또는 Parallel.ForEach 메서드를 사용하여 데이터 소스를 병렬로 반복할 때 스레드 로컬 데이터 지원을 기본으로 제공하는 오버로드를 사용할 수 있습니다. 이러한 메서드에서는 데이터를 만들고 액세스하며 정리하기 위해 로컬 대리자를 사용하여 스레드 국부성을 달성합니다. 자세한 내용은 방법: 스레드 지역 변수를 사용하여 Parallel.For 루프 작성방법: 파티션 지역 변수를 사용하여 Parallel.ForEach 루프 작성을 참조하세요.

오버헤드가 적은 시나리오에 초기화 지연 사용

다수의 개체를 초기화 지연해야 하는 시나리오에서는 Lazy<T>의 각 개체를 래핑하는 데 너무 많은 메모리 또는 너무 많은 계산 리소스가 필요한지 판별할 수 있습니다. 또는 초기화 지연 노출 방법에 대한 엄격한 요구 사항이 있을 수 있습니다. 이 경우 System.Threading.LazyInitializer 클래스의 static(Visual Basic에서 Shared) 메서드를 사용하여 Lazy<T> 인스턴스에 래핑하지 않고 각 개체의 초기화를 지연할 수 있습니다.

다음 예에서는 하나의 Lazy<T> 개체에 전체 Orders 개체를 래핑하지 않고 필요한 경우에만 개별 Order 개체의 초기화를 지연합니다.

// 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

이 예에서는 루프를 반복할 때마다 초기화 프로시저가 호출됩니다. 다중 스레드 시나리오에서 초기화 프로시저를 호출하는 첫 번째 스레드는 모든 스레드에 해당 값이 표시되는 스레드입니다. 나중에 스레드에서 초기화 프로시저도 호출하지만 해당 결과는 사용하지 않습니다. 이 유형의 잠재적 경합 상태가 허용되지 않는 경우 부울 인수와 동기화 개체를 사용하는 LazyInitializer.EnsureInitialized의 오버로드를 사용합니다.

참고 항목