Verzögerte Initialisierung
Verzögerte Initialisierung eines Objekts bedeutet, dass seine Erstellung bis zur ersten Verwendung verzögert wird. (Für dieses Thema sind die Begriffe verzögerte Initialisierung und verzögerte Instanziierung synonym.) Die verzögerte Initialisierung wird vorwiegend verwendet, um die Leistung zu verbessern, aufwendige Berechnungen zu vermeiden und die Speicheranforderungen des Programms zu reduzieren. Die folgenden Szenarios sind die häufigsten:
Sie verfügen über ein Objekt, dessen Erstellung teuer ist und das möglicherweise nicht vom Programm verwendet wird. Angenommen, in Ihrem Speicher befindet sich ein
Customer
-Objekt mit einerOrders
-Eigenschaft, die ein großes Array ausOrder
-Objekten enthält, das zur Initialisierung eine Datenbankverbindung benötigt. Fordert der Benutzer nie die Anzeige des Orders-Objekts oder die Verwendung der Daten in einer Berechnung an, ist es nicht notwendig, Systemspeicher oder Berechnungszyklen für seine Erstellung zu verwenden. Systemressourcen können geschont werden, wenn Sie die verzögerte Initialisierung für dasOrders
-Objekt mithilfe vonLazy<Orders>
deklarieren, solange das Objekt nicht verwendet wird.Sie verfügen über ein Objekt, dessen Erstellung teuer ist und das daher erst erstellt werden soll, wenn andere teure Vorgänge abgeschlossen sind. Angenommen, das Programm lädt beim Start mehrere Objektinstanzen, von denen allerdings nur einige sofort benötigt werden. Hier kann die Startleistung des Programms verbessert werden, indem die Initialisierung der nicht benötigten Objekte verzögert wird, bis die benötigten Objekte erstellt wurden.
Sie können für die verzögerte Initialisierung Ihren eigenen Code schreiben. Es ist jedoch empfehlenswert, stattdessen Lazy<T> zu verwenden. Lazy<T> und die zugehörigen verwandten Typen unterstützen auch die Threadsicherheit und stellen eine konsistente Richtlinie zur Ausnahmeweitergabe bereit.
In der folgenden Tabelle werden die Typen aufgelistet, die .NET Framework Version 4 bereitstellt, um die verzögerte Initialisierung in verschiedenen Szenarios zu aktivieren.
Typ | BESCHREIBUNG |
---|---|
Lazy<T> | Eine Wrapperklasse, die die Semantik für verzögerte Initialisierung für jeden Klassenbibliotheks- oder benutzerdefinierten Typ bereitstellt. |
ThreadLocal<T> | Ähnlich wie Lazy<T>, außer dass die Semantik für verzögerte Initialisierung threadlokal bereitgestellt wird. Jeder Thread hat Zugriff auf seinen eigenen eindeutigen Wert. |
LazyInitializer | Stellt erweiterte static -Methoden (Shared in Visual Basic) für die verzögerte Initialisierung von Objekten bereit ohne den Mehraufwand einer Klasse. |
Grundlegende verzögerte Initialisierung
Verwenden Sie Lazy<MyType>
(Lazy(Of MyType)
in Visual Basic) wie in folgendem Beispiel gezeigt, um einen Typ mit verzögerter Initialisierung, z.B. MyType
, zu definieren. Wird im Lazy<T>-Konstruktor kein Delegat übergeben, wird der umschlossene Typ mithilfe von Activator.CreateInstance beim ersten Zugriff auf die Value-Eigenschaft erstellt. Verfügt der Typ nicht über einen parameterlosen Konstruktor, wird eine Laufzeitausnahme ausgelöst.
Im folgenden Beispiel wird angenommen, dass Orders
eine Klasse mit einem Array aus Order
-Objekten ist, die aus einer Datenbank abgerufen wurden. Ein Customer
-Objekt enthält eine Instanz von Orders
, je nach Benutzeraktion werden die Daten aus dem Orders
-Objekt jedoch möglicherweise nicht benötigt.
// 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)()
Sie können im Lazy<T>-Konstruktor auch einen Delegaten übergeben, der bei der Erstellung eine bestimmte Konstruktorüberladung für den umschlossenen Typ aufruft. Außerdem können Sie die weiteren erforderlichen Initialisierungsschritte wie im folgenden Beispiel gezeigt ausführen.
// 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))
Nachdem das Lazy-Objekt erstellt wurde, wird erst eine Instanz von Orders
erstellt, wenn auf die Value-Eigenschaft der Lazy-Variable zum ersten Mal zugegriffen wird. Beim ersten Zugriff wird der umschlossene Typ erstellt, zurückgegeben und für einen späteren Zugriff gespeichert.
// 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
Ein Lazy<T>-Objekt gibt immer das gleiche Objekt oder den gleichen Wert zurück, mit dem es initialisiert wurde. Daher besitzt die Value-Eigenschaft nur einen Lesezugriff. Wenn Value einen Verweistyp speichert, können Sie ihm kein neues Objekt zuweisen. (Sie können allerdings den Wert seiner festlegbaren öffentlichen Felder und Eigenschaften ändern.) Wenn Value einen Werttyp speichert, können Sie seinen Wert nicht ändern. Sie können jedoch eine neue Variable erstellen, indem Sie den Variablenkonstruktor erneut mit neuen Argumenten aufrufen.
_orders = new Lazy<Orders>(() => new Orders(10));
_orders = New Lazy(Of Orders)(Function() New Orders(10))
Die neue verzögerte Instanz instanziiert das Orders
-Objekt wie die vorherige erst, wenn auf die Value-Eigenschaft erstmals zugegriffen wird.
Threadsichere Initialisierung
Standardmäßig sind Lazy<T>-Objekte threadsicher. Wird im Konstruktor die Art der Threadsicherheit nicht angegeben, sind die erstellten Lazy<T>-Objekte daher threadsicher. In Multithreadszenarios initialisiert der erste Thread, der auf die Value-Eigenschaft eines threadsicheren Lazy<T>-Objekts zugreift, die Eigenschaft für jeden nachfolgenden Zugriff auf allen Threads. Alle Threads nutzen dieselben Daten. Daher spielt es keine Rolle, welcher Thread das Objekt initialisiert. Auch Racebedingungen haben keine Auswirkung.
Hinweis
Diese Konsistenz kann durch das Zwischenspeichern von Ausnahmen auf Fehlerbedingungen erweitert werden. Weitere Informationen finden Sie im folgenden Abschnitt Ausnahmen bei verzögerten Objekten.
Das folgende Beispiel verdeutlicht, dass die gleiche Lazy<int>
-Instanz über den gleichen Wert für drei separate Threads verfügt.
// 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.
Wenn Sie separate Daten für jeden Thread benötigen, verwenden Sie wie weiter unten beschrieben den ThreadLocal<T>-Typ.
Einige Lazy<T>-Konstruktoren verfügen über einen booleschen Parameter mit dem Namen isThreadSafe
, der angibt, ob auf die Value-Eigenschaft von mehreren Threads zugegriffen wird. Wenn Sie von nur einem Thread auf die Eigenschaft zugreifen möchten, übergeben Sie false
für einen moderaten Leistungsvorteil. Wenn Sie von mehreren Threads auf die Eigenschaft zugreifen möchten, übergeben Sie true
, um die Lazy<T>-Instanz anzuweisen, die Racebedingungen ordnungsgemäß zu behandeln, von denen ein Thread bei der Initialisierung eine Ausnahme auslöst.
Einige Lazy<T>-Konstruktoren verfügen über einen LazyThreadSafetyMode-Parameter mit dem Namen mode
. Diese Konstruktoren stellen einen zusätzlichen Threadsicherheitsmodus bereit. Entnehmen Sie der folgenden Tabelle, wie die Threadsicherheit eines Lazy<T>-Objekts von den Konstruktorparametern beeinflusst wird, die die Threadsicherheit angeben. Jeder Konstruktor verfügt über höchstens einen solchen Parameter.
Threadsicherheit des Objekts | LazyThreadSafetyMode mode -Parameter |
Boolescher Parameter isThreadSafe |
Keine Threadsicherheitsparameter |
---|---|---|---|
Vollständig threadsicher; nur ein Thread versucht jeweils, den Wert zu initialisieren. | ExecutionAndPublication | true |
Ja. |
Nicht threadsicher. | None | false |
Nicht zutreffend |
Vollständig threadsicher; Threads befinden sich im Race, um den Wert zu initialisieren. | PublicationOnly | Nicht zutreffend | Nicht zutreffend |
Die Tabelle verdeutlicht, dass die Angabe von LazyThreadSafetyMode.ExecutionAndPublication für den mode
-Parameter der Angabe von true
für den isThreadSafe
-Parameter entspricht, ebenso wie die Angabe von LazyThreadSafetyMode.None der Angabe von false
.
Weitere Informationen dazu, worauf Execution
und Publication
verweisen, finden Sie unter LazyThreadSafetyMode.
Die Angabe von LazyThreadSafetyMode.PublicationOnly ermöglicht mehreren Threads, die Initialisierung der Lazy<T>-Instanz zu versuchen. Nur ein Thread kann unter dieser Racebedingung gewinnen. Alle anderen Threads empfangen den Wert, der vom erfolgreichen Thread initialisiert wurde. Wird während der Initialisierung für einen Thread eine Ausnahme ausgelöst, empfängt dieser Thread nicht den vom erfolgreichen Thread festgelegten Wert. Da Ausnahmen nicht zwischengespeichert werden, kann ein nachfolgender Versuch, auf die Value-Eigenschaft zuzugreifen, zu einer erfolgreichen Initialisierung führen. Darin besteht ein Unterschied zur Behandlung von Ausnahmen in anderen Modi, die im folgenden Abschnitt beschrieben werden. Weitere Informationen finden Sie unter der LazyThreadSafetyMode-Enumeration.
Ausnahmen bei verzögerten Objekten
Wie weiter oben erwähnt gibt ein Lazy<T>-Objekt immer das gleiche Objekt oder den gleichen Wert zurück, mit dem es initialisiert wurde. Daher verfügt die Value-Eigenschaft nur über einen Lesezugriff. Wird das Zwischenspeichern von Ausnahmen aktiviert, wird diese Unveränderlichkeit auch auf das Ausnahmeverhalten erweitert. Wird bei einem Objekt mit verzögerter Initialisierung das Zwischenspeichern von Ausnahmen aktiviert und beim ersten Zugriff auf die Value-Eigenschaft durch die Initialisierungsmethode eine Ausnahme ausgelöst, wird die gleiche Ausnahme bei jedem nachfolgenden Versuch, auf die Value-Eigenschaft zuzugreifen, ausgelöst. Anders ausgedrückt wird der Konstruktor des umschlossenen Typs selbst in Multithreadszenarios niemals erneut aufgerufen. Aus diesem Grund kann das Lazy<T>-Objekt nicht bei einem Zugriff eine Ausnahme auslösen und bei einem nachfolgenden Zugriff einen Wert zurückgeben.
Das Zwischenspeichern von Ausnahmen wird bei der Verwendung eines beliebigen System.Lazy<T>-Konstruktors aktiviert, der eine Initialisierungsmethode erfordert (valueFactory
-Parameter). So wird es beispielsweise aktiviert, wenn Sie den Lazy(T)(Func(T))
-Konstruktor verwenden. Erfordert der Konstruktor auch einen LazyThreadSafetyMode-Wert (mode
-Parameter), geben Sie LazyThreadSafetyMode.ExecutionAndPublication oder LazyThreadSafetyMode.None an. Durch die Angabe einer Initialisierungsmethode wird das Zwischenspeichern von Ausnahmen für diese beiden Modi aktiviert. Die Initialisierungsmethode kann sehr einfach sein. Sie kann z. B. den parameterlosen Konstruktor für T
aufrufen: new Lazy<Contents>(() => new Contents(), mode)
in C# bzw. New Lazy(Of Contents)(Function() New Contents())
in Visual Basic. Wenn Sie einen System.Lazy<T>-Konstruktor verwenden, der keine Initialisierungsmethode angibt, werden vom parameterlosen Konstruktor für T
ausgelöste Ausnahmen nicht zwischengespeichert. Weitere Informationen finden Sie unter der LazyThreadSafetyMode-Enumeration.
Hinweis
Wenn der isThreadSafe
-Konstruktorparameter beim Erstellen eines Lazy<T>-Objekts auf false
festgelegt ist oder der mode
-Konstruktorparameter auf LazyThreadSafetyMode.None, müssen Sie auf das Lazy<T>-Objekt von einem einzelnen Thread zugreifen oder eine eigene Synchronisierung bereitstellen. Dies gilt für alle Aspekte des Objekts, einschließlich dem Zwischenspeichern von Ausnahmen.
Wie im vorherigen Abschnitt erwähnt, behandeln durch Angabe von LazyThreadSafetyMode.PublicationOnly erstellte Lazy<T>-Objekte Ausnahmen unterschiedlich. Mit PublicationOnly können mehrere Threads um die Initialisierung der Lazy<T>-Instanz konkurrieren. In diesem Fall werden Ausnahmen nicht zwischengespeichert. Die Versuche, auf die Value-Eigenschaft zuzugreifen, können fortgesetzt werden, bis die Initialisierung erfolgreich ist.
In der folgenden Tabelle werden die Steuerungsmethoden für das Zwischenspeichern von Ausnahmen durch Lazy<T>-Konstruktoren zusammengefasst.
Konstruktor | Threadsicherheitsmodus | Verwendet die Initialisierungsmethode | Ausnahmen werden zwischengespeichert |
---|---|---|---|
Lazy(T)() | (ExecutionAndPublication) | Nein | Nein |
Lazy(T)(Func(T)) | (ExecutionAndPublication) | Ja | Ja |
Lazy(T)(Boolean) | True (ExecutionAndPublication) oder false (None) |
Nein | Nein |
Lazy(T)(Func(T), Boolean) | True (ExecutionAndPublication) oder false (None) |
Ja | Ja |
Lazy(T)(LazyThreadSafetyMode) | Vom Benutzer angegeben | Nein | Nein |
Lazy(T)(Func(T), LazyThreadSafetyMode) | Vom Benutzer angegeben | Ja | Nein, wenn der Benutzer PublicationOnly angibt; andernfalls ja. |
Implementieren einer Eigenschaft mit verzögerter Initialisierung
Definieren Sie zum Implementieren einer öffentlichen Eigenschaft mit der verzögerten Initialisierung das Unterstützungsfeld der Eigenschaft als Lazy<T>, und geben Sie die Value-Eigenschaft des get
-Accessors der Eigenschaft zurück.
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
Die Value-Eigenschaft besitzt nur Lesezugriff. Daher verfügt die Eigenschaft, die sie verfügbar macht, über keinen set
-Accessor. Ist eine Lese-/Schreibeigenschaft erforderlich, die durch ein Lazy<T>-Objekt gesichert wird, muss der set
-Accessor ein neues Lazy<T>-Objekt erstellen und dem Sicherungsspeicher zuweisen. Der set
-Accessor muss einen Lambdaausdruck erstellen, der den an den set
-Accessor übergebenen neuen Eigenschaftswert zurückgibt. Dieser Lambdaausdruck muss anschließend an den Konstruktor für das neue Lazy<T>-Objekt übergeben werden. Der nächste Zugriff auf die Value-Eigenschaft führt zur Initialisierung des neuen Lazy<T>. Die zugehörige Value-Eigenschaft gibt danach den neuen Wert zurück, der der Eigenschaft zugewiesen wurde. Der Grund für diese komplizierte Gestaltung besteht darin, dass der in Lazy<T> integrierte Multithreadingschutz beibehalten werden soll. Andernfalls müssten die Eigenschaftenaccessoren den ersten von der Value-Eigenschaft zurückgegebenen Wert zwischenspeichern und nur den zwischengespeicherten Wert ändern. Dafür müssten Sie Ihren eigenen threadsicheren Code schreiben. Aufgrund der zusätzlichen Initialisierungen, die von einer durch ein Lazy<T>-Objekt gesicherten Lese-/Schreibeigenschaft angefordert werden, ist die Leistung möglicherweise nicht akzeptabel. Darüber hinaus kann je nach Szenario eine zusätzliche Koordinierung erforderlich sein, um Racebedingungen zwischen Setter und Getter zu vermeiden.
Threadlokale verzögerte Initialisierung
In einigen Multithreadszenarios möchten Sie jedem Thread möglicherweise eigene private Daten zuweisen. Diese Daten werden threadlokale Daten genannt. Bis .NET Framework Version 3.5 konnte das ThreadStatic
-Attribut auf eine statische Variable angewendet werden, um aus ihr eine threadlokale Variable zu machen. Die Anwendung des ThreadStatic
-Attributs kann jedoch geringfügige Fehler verursachen. Selbst grundlegende Initialisierungsanweisungen führen z.B. dazu, dass die Variable wie im folgenden Beispiel gezeigt nur für den ersten Thread initialisiert wird, der auf sie zugreift.
[ThreadStatic]
static int counter = 1;
<ThreadStatic()>
Shared counter As Integer
Für alle anderen Threads wird die Variable mit ihrem Standardwert (0) initialisiert. In .NET Framework Version 4 kann der System.Threading.ThreadLocal<T>-Typ alternativ dazu zum Erstellen einer instanzbasierten, threadlokalen Variable verwendet werden, die für alle Threads mit dem von Ihnen bereitgestellten Action<T>-Delegaten initialisiert wird. Im folgenden Beispiel erhalten alle Threads, die auf counter
zugreifen, den Startwert 1.
ThreadLocal<int> betterCounter = new ThreadLocal<int>(() => 1);
Dim betterCounter As ThreadLocal(Of Integer) = New ThreadLocal(Of Integer)(Function() 1)
ThreadLocal<T> umschließt sein Objekt auf fast die gleiche Weise wie Lazy<T>, mit folgenden entscheidenden Unterschieden:
Jeder Thread initialisiert die threadlokale Variable mit seinen eigenen privaten Daten, auf die von anderen Threads nicht zugegriffen werden kann.
Die ThreadLocal<T>.Value-Eigenschaft verfügt über Lese-/Schreibzugriff und kann beliebig oft geändert werden. Dies kann sich auf die Ausnahmeweitergabe auswirken: So kann ein
get
-Vorgang z.B. eine Ausnahme auslösen, während der nächste den Wert erfolgreich initialisiert.ThreadLocal<T> initialisiert seinen umschlossenen Typ mit dem Standardwert des Typs, wenn kein Initialisierungsdelegat bereitgestellt wird. In dieser Hinsicht ist ThreadLocal<T> mit dem ThreadStaticAttribute-Attribut konsistent.
Das folgende Beispiel verdeutlicht, dass jeder auf die ThreadLocal<int>
-Instanz zugreifende Thread eine eigene eindeutige Kopie der Daten erhält.
// 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
Threadlokale Variablen in Parallel.For und ForEach
Beim parallelen Durchlaufen von Datenquellen mit den Methoden Parallel.For oder Parallel.ForEach können Sie Überladungen mit integrierter Unterstützung für threadlokale Daten verwenden. Bei diesen Methoden wird die Threadlokalität mithilfe von lokalen Delegaten erzielt, um die Daten zu erstellen, auf sie zuzugreifen und sie zu bereinigen. Weitere Informationen finden Sie unter Vorgehensweise: Schreiben einer Parallel.For-Schleife mit threadlokalen Variablen und Vorgehensweise: Schreiben einer Parallel.ForEach-Schleife mit partitionslokalen Variablen.
Verwenden der verzögerten Initialisierung für Szenarios mit geringem Mehraufwand
In Szenarios mit einer großen Anzahl von Objekten, die verzögert initialisiert werden sollen, würde das Umschließen jedes einzelnen Objekts mit einem Lazy<T> möglicherweise zu viel Arbeitsspeicher oder zu viele Computerressourcen erfordern. Möglicherweise bestehen auch strenge Anforderungen an die Art, wie die verzögerte Initialisierung verfügbar gemacht wird. In diesen Fällen können Sie die static
-Methoden (Shared
in Visual Basic) der System.Threading.LazyInitializer-Klasse zur Initialisierung jedes Objekts verwenden, ohne es mit einer Instanz von Lazy<T> zu umschließen.
Im folgenden Beispiel wird angenommen, dass einzelne Order
-Objekte nur nach Bedarf verzögert initialisiert werden, anstatt ein ganzes Orders
-Objekt mit einem Lazy<T>-Objekt zu umschließen.
// 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
Beachten Sie, dass in diesem Beispiel die Initialisierungsprozedur bei jeder Iteration der Schleife aufgerufen wird. In Multithreadszenarios ist der erste Thread, der die Initialisierungsprozedur aufruft, derjenige, dessen Wert für alle Threads sichtbar ist. Zwar rufen auch spätere Threads die Initialisierungsprozedur auf, doch werden ihre Ergebnisse nicht verwendet. Ist diese Art von potenzieller Racebedingung nicht akzeptabel, verwenden Sie die Überladung von LazyInitializer.EnsureInitialized, die ein boolesches Argument und ein Synchronisierungsobjekt erfordert.