Udostępnij za pośrednictwem


Inicjalizacja z opóźnieniem

Opóźnione inicjowanie obiektu oznacza, że jego tworzenie jest odroczone do momentu pierwszego użycia. (W tym temacie terminy z opóźnieniem inicjowania i leniwe wystąpienia są synonimami). Inicjowanie z opóźnieniem jest używane głównie w celu zwiększenia wydajności, uniknięcia marnowania obliczeń i zmniejszenia wymagań dotyczących pamięci programu. Są to najbardziej typowe scenariusze:

  • Jeśli masz obiekt, który jest kosztowny do utworzenia, a program może go nie używać. Załóżmy na przykład, że w pamięci Customer znajduje się obiekt, który ma Orders właściwość zawierającą dużą tablicę Order obiektów, które mają zostać zainicjowane, wymagają połączenia z bazą danych. Jeśli użytkownik nigdy nie prosi o wyświetlenie zamówień lub użycie danych w obliczeniach, nie ma powodu używania pamięci systemowej ani cykli obliczeniowych do ich utworzenia. Za pomocą polecenia Lazy<Orders> w celu zadeklarowania Orders obiektu w przypadku opóźnionia inicjowania można uniknąć marnowania zasobów systemowych, gdy obiekt nie jest używany.

  • Jeśli masz obiekt, który jest kosztowny do utworzenia i chcesz odroczyć jego tworzenie do momentu zakończenia innych kosztownych operacji. Załóżmy na przykład, że program ładuje kilka wystąpień obiektów podczas uruchamiania, ale tylko niektóre z nich są wymagane natychmiast. Wydajność uruchamiania programu można poprawić, odroczając inicjowanie obiektów, które nie są wymagane do momentu utworzenia wymaganych obiektów.

Mimo że możesz napisać własny kod, aby wykonać leniwą inicjację, zalecamy użycie zamiast Lazy<T> tego. Lazy<T> i jego powiązane typy obsługują również bezpieczeństwo wątków i zapewniają spójne zasady propagacji wyjątków.

W poniższej tabeli wymieniono typy, które program .NET Framework w wersji 4 udostępnia w celu włączenia inicjowania z opóźnieniem w różnych scenariuszach.

Type Opis
Lazy<T> Klasa otoki, która zapewnia semantykę inicjowania z opóźnieniem dla dowolnej biblioteki klas lub typu zdefiniowanego przez użytkownika.
ThreadLocal<T> Lazy<T> Przypomina, z tą różnicą, że zapewnia leniwą semantyka inicjowania na podstawie wątku lokalnego. Każdy wątek ma dostęp do własnej unikatowej wartości.
LazyInitializer Udostępnia zaawansowane static (Shared w Visual Basic) metody opóźnionego inicjowania obiektów bez nakładu pracy nad klasą.

Inicjowanie z opóźnieniem podstawowym

Aby zdefiniować typ zainicjowany z opóźnieniem, na przykład MyType, użyj ( Lazy<MyType>Lazy(Of MyType) w Visual Basic), jak pokazano w poniższym przykładzie. Jeśli żaden delegat nie zostanie przekazany w konstruktorze Lazy<T> , typ opakowany zostanie utworzony przy użyciu Activator.CreateInstance , gdy właściwość value jest najpierw uzyskiwana. Jeśli typ nie ma konstruktora bez parametrów, zgłaszany jest wyjątek czasu wykonywania.

W poniższym przykładzie przyjęto założenie, że Orders jest to klasa zawierająca tablicę obiektów pobranych Order z bazy danych. Obiekt Customer zawiera wystąpienie Ordersobiektu , ale w zależności od akcji użytkownika dane z Orders obiektu mogą nie być wymagane.

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

Można również przekazać delegata w konstruktorze Lazy<T> , który wywołuje określone przeciążenie konstruktora na opakowanym typie w czasie tworzenia, i wykonać inne wymagane kroki inicjowania, jak pokazano w poniższym przykładzie.

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

Po utworzeniu obiektu Lazy nie jest tworzone żadne wystąpienie Orders , dopóki Value właściwość zmiennej Lazy nie będzie dostępna po raz pierwszy. Po pierwszym dostępie opakowany typ jest tworzony i zwracany oraz przechowywany dla dowolnego przyszłego dostępu.

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

Obiekt Lazy<T> zawsze zwraca ten sam obiekt lub wartość, z którą został zainicjowany. W związku z tym Value właściwość jest tylko do odczytu. Jeśli Value przechowuje typ odwołania, nie można przypisać do niego nowego obiektu. (Można jednak zmienić wartość jego ustawianych pól publicznych i właściwości). Jeśli Value przechowuje typ wartości, nie można zmodyfikować jej wartości. Niemniej jednak można utworzyć nową zmienną, wywołując ponownie konstruktor zmiennej przy użyciu nowych argumentów.

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

Nowe wystąpienie leniwe, takie jak wcześniejsza, nie tworzy wystąpienia Orders , dopóki jej Value właściwość nie zostanie po raz pierwszy zwrócona.

Inicjowanie bezpieczne wątkowo

Domyślnie Lazy<T> obiekty są bezpieczne wątkowo. Oznacza to, że jeśli konstruktor nie określa rodzaju bezpieczeństwa wątków, Lazy<T> tworzone przez niego obiekty są bezpieczne wątkowo. W scenariuszach wielowątowych pierwszy wątek umożliwiający dostęp do Value właściwości obiektu bezpiecznego Lazy<T> wątkowo inicjuje go dla wszystkich kolejnych dostępu we wszystkich wątkach, a wszystkie wątki współdzielą te same dane. W związku z tym nie ma znaczenia, który wątek inicjuje obiekt, a warunki wyścigu są łagodne.

Uwaga

Tę spójność można rozszerzyć na warunki błędów przy użyciu buforowania wyjątków. Aby uzyskać więcej informacji, zobacz następną sekcję Wyjątki w obiektach z opóźnieniem.

W poniższym przykładzie pokazano, że to samo wystąpienie ma tę samą Lazy<int> wartość dla trzech oddzielnych wątków.

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

Jeśli potrzebujesz oddzielnych danych w każdym wątku, użyj ThreadLocal<T> typu , zgodnie z opisem w dalszej części tego tematu.

Niektóre Lazy<T> konstruktory mają parametr logiczny o nazwie isThreadSafe , który służy do określenia, czy Value dostęp do właściwości będzie uzyskiwany z wielu wątków. Jeśli zamierzasz uzyskać dostęp do właściwości tylko z jednego wątku, przekaż go false , aby uzyskać skromną korzyść z wydajności. Jeśli zamierzasz uzyskać dostęp do właściwości z wielu wątków, przekaż polecenie true , aby Lazy<T> wystąpienie prawidłowo obsługiwało warunki wyścigu, w których jeden wątek zgłasza wyjątek w czasie inicjowania.

Niektóre Lazy<T> konstruktory mają LazyThreadSafetyMode parametr o nazwie mode. Te konstruktory zapewniają dodatkowy tryb bezpieczeństwa wątków. W poniższej tabeli pokazano, jak bezpieczeństwo wątku Lazy<T> obiektu ma wpływ na parametry konstruktora, które określają bezpieczeństwo wątku. Każdy konstruktor ma co najwyżej jeden taki parametr.

Bezpieczeństwo wątku obiektu LazyThreadSafetyModemode parametr Parametr logiczny isThreadSafe Brak parametrów bezpieczeństwa wątku
W pełni bezpieczne wątkowo; tylko jeden wątek jednocześnie próbuje zainicjować wartość. ExecutionAndPublication true Tak.
Nie jest bezpieczne wątkowo. None false Nie dotyczy.
W pełni bezpieczne wątkowo; wątki ścigają się, aby zainicjować wartość. PublicationOnly Nie dotyczy. Nie dotyczy.

Jak pokazano w tabeli, określenie LazyThreadSafetyMode.ExecutionAndPublication parametru mode jest takie samo, jak określenie true parametru isThreadSafe , a określenie LazyThreadSafetyMode.None parametru jest takie samo, jak określanie falseparametru .

Aby uzyskać więcej informacji na temat tego, co Execution i Publication zobacz LazyThreadSafetyMode.

Określenie LazyThreadSafetyMode.PublicationOnly umożliwia wielu wątkom próbę zainicjowania Lazy<T> wystąpienia. Tylko jeden wątek może wygrać ten wyścig, a wszystkie pozostałe wątki otrzymują wartość zainicjowaną przez udany wątek. Jeśli podczas inicjowania zgłaszany jest wyjątek, ten wątek nie otrzymuje wartości ustawionej przez pomyślny wątek. Wyjątki nie są buforowane, więc kolejna próba uzyskania dostępu Value do właściwości może spowodować pomyślne zainicjowanie. Różni się to od sposobu traktowania wyjątków w innych trybach, które opisano w poniższej sekcji. Aby uzyskać więcej informacji, zobacz LazyThreadSafetyMode wyliczenie.

Wyjątki w obiektach z opóźnieniem

Jak wspomniano wcześniej, Lazy<T> obiekt zawsze zwraca ten sam obiekt lub wartość, z którą został zainicjowany, a zatem Value właściwość jest tylko do odczytu. Jeśli włączysz buforowanie wyjątków, ta niezmienność również rozszerza zachowanie wyjątków. Jeśli obiekt zainicjowany z opóźnieniem ma włączoną buforowanie wyjątków i zgłasza wyjątek od metody inicjowania, gdy Value właściwość jest najpierw uzyskiwana, ten sam wyjątek jest zgłaszany podczas każdej kolejnej próby uzyskania dostępu do Value właściwości. Innymi słowy, konstruktor opakowanego typu nigdy nie jest wywoływany ponownie, nawet w scenariuszach wielowątków. Lazy<T> W związku z tym obiekt nie może zgłosić wyjątku dla jednego dostępu i zwrócić wartość dla kolejnego dostępu.

Buforowanie wyjątków jest włączone, gdy używasz dowolnego System.Lazy<T> konstruktora, który przyjmuje metodę inicjowania (valueFactory parametr), na przykład jest on włączony podczas korzystania z konstruktora Lazy(T)(Func(T)). Jeśli konstruktor przyjmuje LazyThreadSafetyMode również wartość (mode parametr), określ LazyThreadSafetyMode.ExecutionAndPublication wartość lub LazyThreadSafetyMode.None. Określenie metody inicjowania umożliwia buforowanie wyjątków dla tych dwóch trybów. Metoda inicjowania może być bardzo prosta. Na przykład może wywołać konstruktor bez parametrów dla Telementu : new Lazy<Contents>(() => new Contents(), mode) w języku C# lub New Lazy(Of Contents)(Function() New Contents()) w Visual Basic. Jeśli używasz konstruktora System.Lazy<T> , który nie określa metody inicjowania, wyjątki zgłaszane przez konstruktor bez parametrów nie T są buforowane. Aby uzyskać więcej informacji, zobacz LazyThreadSafetyMode wyliczenie.

Uwaga

Jeśli utworzysz Lazy<T> obiekt z parametrem konstruktora ustawionym isThreadSafe na false lub mode parametr konstruktora ustawionym na LazyThreadSafetyMode.None, musisz uzyskać dostęp do Lazy<T> obiektu z pojedynczego wątku lub zapewnić własną synchronizację. Dotyczy to wszystkich aspektów obiektu, w tym buforowania wyjątków.

Jak wspomniano w poprzedniej sekcji, Lazy<T> obiekty utworzone przez określenie wyjątków traktują LazyThreadSafetyMode.PublicationOnly inaczej. W programie PublicationOnlywiele wątków może konkurować w celu zainicjowania Lazy<T> wystąpienia. W takim przypadku wyjątki nie są buforowane, a próby uzyskania dostępu do właściwości mogą być kontynuowane do momentu pomyślnego Value zainicjowania.

Poniższa tabela zawiera podsumowanie sposobu Lazy<T> buforowania wyjątków kontrolek konstruktorów.

Konstruktor Tryb bezpieczeństwa wątków Używa metody inicjowania Wyjątki są buforowane
Leniwy(T)() (ExecutionAndPublication) Nie Nie.
Lazy(T)(Func(T)) (ExecutionAndPublication) Tak Tak
Lazy(T)(Wartość logiczna) True (ExecutionAndPublication) lub false (None) Nie Nie.
Lazy(T)(Func(T), Wartość logiczna) True (ExecutionAndPublication) lub false (None) Tak Tak
Lazy(T)(LazyThreadSafetyMode) Określony przez użytkownika Nie Nie.
Lazy(T)(Func(T), LazyThreadSafetyMode) Określony przez użytkownika Tak Nie, jeśli użytkownik określa PublicationOnly; w przeciwnym razie tak.

Implementowanie właściwości zainicjowanej z opóźnieniem

Aby zaimplementować właściwość publiczną przy użyciu inicjowania z opóźnieniem Lazy<T>, zdefiniuj pole zapasowe właściwości jako , a następnie zwróć Value właściwość z get metody dostępu do właściwości .

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

Właściwość Value jest tylko do odczytu, dlatego właściwość, która uwidacznia ją, nie set ma metody dostępu. Jeśli potrzebujesz właściwości odczytu/zapisu wspieranej Lazy<T> przez obiekt, set metoda dostępu musi utworzyć nowy Lazy<T> obiekt i przypisać go do magazynu kopii zapasowych. Metoda set dostępu musi utworzyć wyrażenie lambda zwracające nową wartość właściwości, która została przekazana do set metody dostępu, i przekazać to wyrażenie lambda do konstruktora dla nowego Lazy<T> obiektu. Następny dostęp do Value właściwości spowoduje zainicjowanie nowej Lazy<T>właściwości , a jej Value właściwość zwróci nową wartość przypisaną do właściwości . Przyczyną tego zawiłego układu jest zachowanie ochrony wielowątku wbudowanej w Lazy<T>element . W przeciwnym razie metody dostępu do właściwości musiałyby buforować pierwszą wartość zwracaną przez Value właściwość i modyfikować tylko buforowaną wartość, a w tym celu należy napisać własny kod bezpieczny wątkowo. Ze względu na dodatkowe inicjowanie wymagane przez właściwość odczytu/zapisu wspierane przez Lazy<T> obiekt, wydajność może nie być akceptowalna. Ponadto w zależności od konkretnego scenariusza może być wymagana dodatkowa koordynacja w celu uniknięcia warunków wyścigu między seterami i metodami pobierania.

Inicjowanie z opóźnieniem wątku lokalnego

W niektórych scenariuszach wielowątkowych warto nadać każdemu wątkowi własne dane prywatne. Takie dane są nazywane danymi lokalnymi wątku. W programie .NET Framework w wersji 3.5 lub starszej można zastosować ThreadStatic atrybut do zmiennej statycznej, aby utworzyć go w wątku lokalnego. Jednak użycie atrybutu ThreadStatic może prowadzić do drobnych błędów. Na przykład nawet podstawowe instrukcje inicjowania spowodują zainicjowanie zmiennej tylko w pierwszym wątku, który uzyskuje do niej dostęp, jak pokazano w poniższym przykładzie.

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

We wszystkich innych wątkach zmienna zostanie zainicjowana przy użyciu jej wartości domyślnej (zero). Alternatywnie w programie .NET Framework w wersji 4 można użyć System.Threading.ThreadLocal<T> typu do utworzenia zmiennej lokalnej opartej na wystąpieniu, która jest inicjowana we wszystkich wątkach przez podanego delegata Action<T> . W poniższym przykładzie wszystkie wątki, do których uzyskuje dostęp counter , będą widzieć jego wartość początkową jako 1.

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

ThreadLocal<T> owija swój obiekt w taki sam sposób jak Lazy<T>, z tymi istotnymi różnicami:

  • Każdy wątek inicjuje zmienną lokalną thread-local przy użyciu własnych danych prywatnych, które nie są dostępne z innych wątków.

  • Właściwość ThreadLocal<T>.Value jest odczyt-zapis i może być modyfikowana dowolną liczbę razy. Może to mieć wpływ na propagację wyjątku, na przykład jedna get operacja może zgłosić wyjątek, ale następny może pomyślnie zainicjować wartość.

  • Jeśli nie podano delegata inicjalizacji, ThreadLocal<T> zainicjuje jego typ opakowany przy użyciu wartości domyślnej typu. W tym względzie ThreadLocal<T> jest zgodny z atrybutem ThreadStaticAttribute .

W poniższym przykładzie pokazano, że każdy wątek, który uzyskuje ThreadLocal<int> dostęp do wystąpienia, pobiera własną unikatową kopię danych.

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

Zmienne wątkowo-lokalne równolegle.For i ForEach

Korzystając z Parallel.For metody lub Parallel.ForEach metody do iterowania równoległych źródeł danych, można użyć przeciążeń, które mają wbudowaną obsługę danych wątkowych lokalnych. W tych metodach lokalność wątków jest osiągana przy użyciu delegatów lokalnych do tworzenia, uzyskiwania dostępu i czyszczenia danych. Aby uzyskać więcej informacji, zobacz How to: Write a Parallel.For Loop with Thread-Local Variables (Instrukcje: zapisywanie pętli Parallel.ForEach za pomocą zmiennych lokalnych partycji).

Używanie inicjowania z opóźnieniem dla scenariuszy niskiego obciążenia

W scenariuszach, w których trzeba opóźnić inicjowanie dużej liczby obiektów, można zdecydować, że zawijanie każdego obiektu w obiekcie Lazy<T> wymaga zbyt dużej ilości pamięci lub zbyt wielu zasobów obliczeniowych. Możesz też mieć rygorystyczne wymagania dotyczące sposobu uwidocznienia leniwej inicjalizacji. W takich przypadkach można użyć static metod System.Threading.LazyInitializer (Shared w Visual Basic) klasy, aby opóźnić inicjowanie każdego obiektu bez zawijania go w wystąpieniu Lazy<T>klasy .

W poniższym przykładzie przyjęto założenie, że zamiast opakowywać cały Orders obiekt w jednym Lazy<T> obiekcie, pojedyncze obiekty inicjowane Order są z opóźnieniem tylko wtedy, gdy są wymagane.

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

W tym przykładzie zwróć uwagę, że procedura inicjowania jest wywoływana w każdej iteracji pętli. W scenariuszach wielowątkowych pierwszy wątek, który wywołuje procedurę inicjowania, jest tym, którego wartość jest widoczna przez wszystkie wątki. Późniejsze wątki wywołują również procedurę inicjowania, ale ich wyniki nie są używane. Jeśli ten rodzaj potencjalnego stanu wyścigu nie jest akceptowalny, użyj przeciążenia LazyInitializer.EnsureInitialized , które przyjmuje argument logiczny i obiekt synchronizacji.

Zobacz też