Dieser Artikel wurde noch nicht bewertet - Dieses Thema bewerten.

Verzögerte Initialisierung

Verzögerte Initialisierung eines Objekts bedeutet, dass seine Erstellung verzögert wird, bis es erstmalig verwendet wird. (Für dieses Thema sind die Begriffe verzögerte Initialisierung und verzögerte Instanziierung synonym.) Die verzögerte Initialisierung dient hauptsächlich dazu, die Leistung zu verbessern, unnötige Berechnungen zu vermeiden und die Programmspeicheranforderungen zu reduzieren. Im Folgenden sind die häufigsten Szenarien aufgeführt:

  • Sie verfügen über ein Objekt, dessen Erstellung teuer ist und das möglicherweise nicht vom Programm verwendet wird. Angenommen, im Speicher befindet sich ein Customer-Objekt mit einer Orders-Eigenschaft, die ein großes Array von Order-Objekten enthält, für dessen Initialisierung eine Datenbankverbindung erforderlich ist. Wenn der Benutzer nie anfordert, ein Orders-Objekt anzuzeigen oder die Daten in einer Berechnung zu verwenden, gibt es keinen Grund, Systemspeicher oder Berechnungszyklen zu verwenden, um es zu erstellen. Indem Sie Lazy<Orders> verwenden, um die verzögerte Initialisierung für das Orders-Objekt zu deklarieren, können Sie vermeiden, dass Systemressourcen verschwendet werden, wenn das Objekt nicht verwendet wird.

  • Sie verfügen über ein Objekt, dessen Erstellung teuer ist und daher verzögert werden soll, bis andere teure Vorgänge abgeschlossen wurden. Angenommen, das Programm lädt beim Start mehrere Objektinstanzen, aber nur einige davon sind sofort erforderlich. Sie können die Startleistung des Programms verbessern, indem Sie die Initialisierung der nicht erforderlichen Objekte verzögern, bis die erforderlichen Objekte erstellt wurden.

Obwohl Sie eigenen Code schreiben können, um die verzögerte Initialisierung auszuführen, empfiehlt es sich, stattdessen Lazy<T> zu verwenden. Lazy<T> und die dazugehörigen verwandten Typen unterstützen auch Threadsicherheit und stellen eine konsistente Ausnahmeverteilungsrichtlinie bereit.

In der folgenden Tabelle sind die Typen auflistet, die von .NET Framework Version 4 bereitstellt werden, um die verzögerte Initialisierung in verschiedenen Szenarien zu ermöglichen.

Typ

Beschreibung

Lazy<T>

Eine Wrapperklasse, die die Semantik für die verzögerte Initialisierung für jede Klassenbibliothek oder jeden benutzerdefinierten Typ bereitstellt.

ThreadLocal<T>

Ist mit Lazy<T> vergleichbar, außer dass dieser Typ die Semantik für die verzögerte Initialisierung auf threadlokaler Basis bereitstellt. Jeder Thread hat Zugriff auf einen eigenen eindeutigen Wert.

LazyInitializer

Stellt erweiterte static-Methoden (Shared in Visual Basic) für die verzögerte Initialisierung von Objekten ohne den Mehraufwand einer Klasse bereit.

Um einen Typ mit verzögerter Initialisierung zu definieren, z. B. MyType, verwenden Sie Lazy<MyType> (Lazy(Of MyType) in Visual Basic), wie im folgenden Beispiel gezeigt. Wenn kein Delegat im Lazy<T>-Konstruktor übergeben wird, wird der umschlossene Typ mit Activator.CreateInstance erstellt, wenn erstmals auf die Werteigenschaft zugegriffen wird. Wenn der Typ nicht über einen Standardkonstruktor verfügt, wird eine Laufzeitausnahme ausgelöst.

Im folgenden Beispiel wird angenommen, dass Orders eine Klasse ist, die ein Array von aus einer Datenbank abgerufenen Order-Objekten enthält. Ein Customer-Objekt enthält eine Instanz von Orders, aber abhängig von Benutzeraktionen sind die Daten aus dem Orders-Objekt möglicherweise nicht erforderlich.


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


Sie können auch einen Delegaten im Lazy<T>-Konstruktor übergeben, der eine bestimmte Konstruktorüberladung zur Erstellungszeit für den umschlossenen Typ aufruft, und beliebige andere Initialisierungsschritte ausführen, die erforderlich sind, wie im folgenden Beispiel gezeigt.


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


Nachdem das verzögerte Objekt erstellt wurde, wird keine Instanz von Orders erstellt, bis erstmals auf die Value-Eigenschaft der Lazy-Variable zugegriffen wird. Beim erstem Zugriff wird der umschlossene Typ erstellt und zurückgegeben und für einen zukünftigen 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.
}


Ein Lazy<T>-Objekt gibt immer das gleiche Objekt oder den gleichen Wert zurück, mit dem es initialisiert wurde. Die Value-Eigenschaft ist daher schreibgeschützt. Wenn Value einen Verweistyp speichert, können Sie ihm kein neues Objekt zuweisen. (Sie können jedoch den Wert der festlegbaren öffentlichen Felder und Eigenschaften des Verweistyps ändern.) Wenn Value einen Werttyp speichert, können Sie dessen Wert nicht ändern. Trotzdem können Sie eine neue Variable erstellen, indem Sie den variablen Konstruktor erneut mit neuen Argumenten aufrufen.


_orders = new Lazy<Orders>(() => new Orders(10));


Die neue verzögerte Instanz instanziiert – wie die vorherige – das Orders-Objekt erst, wenn erstmals auf die Value-Eigenschaft zugegriffen wird.

Dd997286.collapse_all(de-de,VS.110).gifThreadsichere Initialisierung

Standardmäßig sind Lazy<T>-Objekte threadsicher. Das heißt, wenn der Konstruktor die Art der Threadsicherheit nicht angibt, sind die von diesem erstellten Lazy<T>-Objekte threadsicher. In Multithreadszenarien wird die Value-Eigenschaft eines threadsicheren Lazy<T>-Objekts durch den ersten Thread, der auf die Eigenschaft zugreift, für alle nachfolgenden Zugriffe auf allen Threads initialisiert, und alle Threads verwenden die gleichen Daten. Daher ist es nicht relevant, welcher Thread das Objekt initialisiert, und Racebedingungen haben keine Auswirkung.

Hinweis Hinweis

Sie können diese Konsistenz auf Fehlerzustände erweitern, indem Sie eine Zwischenspeicherung für Ausnahmen verwenden. Weitere Informationen finden Sie im nächsten Abschnitt Exceptions in Lazy Objects.

Das folgende Beispiel zeigt, 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.
*/


Wenn Sie separate Daten für jeden Thread benötigen, verwenden Sie den ThreadLocal<T>-Typ, wie weiter unten in diesem Thema beschrieben.

Einige Lazy<T>-Konstruktoren besitzen den booleschen Parameter isThreadSafe, mit dem angegeben wird, ob auf die Value-Eigenschaft von mehreren Threads zugegriffen wird. Wenn Sie beabsichtigen, dass nur von einem Thread auf die Eigenschaft zugegriffen wird, übergeben Sie false, um einen bescheidenen Leistungsvorteil zu erhalten. Wenn von mehreren Threads auf die Eigenschaft zugriffen werden soll, übergeben Sie true, um die Lazy<T>-Instanz anzuweisen, die Racebedingungen, unter denen ein Thread eine Ausnahme zur Initialisierungszeit auslöst, ordnungsgemäß zu behandeln.

Einige Lazy<T>-Konstruktoren besitzen einen LazyThreadSafetyMode-Parameter mit dem Namen mode. Diese Konstruktoren stellen einen zusätzlichen Threadsicherheitsmodus bereit. Die folgende Tabelle zeigt, wie die Threadsicherheit eines Lazy<T>-Objekts von Konstruktorparametern betroffen ist, die Threadsicherheit angeben. Jeder Konstruktor verfügt höchstens über einen solchen Parameter.

Threadsicherheit des Objekts

mode-Parameter von LazyThreadSafetyMode

Boolescher isThreadSafe-Parameter

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 Racezustand, um den Wert zu initialisieren.

PublicationOnly

Nicht zutreffend.

Nicht zutreffend.

Wie die Tabelle zeigt, ist das Angeben von LazyThreadSafetyMode.ExecutionAndPublication für den mode-Parameter mit dem Angeben von true für den isThreadSafe-Parameter gleichwertig, und das Angeben von LazyThreadSafetyMode.None ist mit dem Angeben von false gleichwertig.

Wenn Sie LazyThreadSafetyMode.PublicationOnly angeben, können mehrere Threads versuchen, die Lazy<T>-Instanz zu initialisieren. Nur ein Thread kann unter dieser Racebedingung gewinnen, und alle anderen Threads empfangen den Wert, der vom erfolgreichen Thread initialisiert wurde. Wenn während der Initialisierung eine Ausnahme für einen Thread ausgelöst wird, empfängt der betreffende Thread nicht den vom erfolgreichen Thread festgelegten Wert. Ausnahmen werden nicht zwischengespeichert, deshalb kann ein späterer Versuch, auf die Value-Eigenschaft zuzugreifen, zu einer erfolgreichen Initialisierung führen. Dies weicht von der Weise ab, in der Ausnahmen in anderen Modi behandelt werden, wie im folgenden Abschnitt beschrieben. Weitere Informationen finden Sie unter der LazyThreadSafetyMode-Enumeration.

Wie zuvor angegeben, gibt ein Lazy<T>-Objekt immer das gleiche Objekt oder den gleichen Wert zurück, mit dem es initialisiert wurde. Daher ist die Value-Eigenschaft schreibgeschützt. Wenn Sie die Zwischenspeicherung für Ausnahmen aktivieren, gilt diese Unveränderlichkeit auch für das Ausnahmeverhalten. Wenn die Zwischenspeicherung für Ausnahmen für ein verzögert initialisiertes Objekt aktiviert ist und in der Initialisierungsmethode beim ersten Zugriff auf die Value-Eigenschaft eine Ausnahme ausgelöst wird, wird die gleiche Ausnahme bei jedem nachfolgenden Versuch, auf die Value-Eigenschaft zuzugreifen, ausgelöst. Dies bedeutet, dass der Konstruktor des umschlossenen Typs nie erneut aufgerufen wird, auch nicht in Multithreadszenarien. Daher kann das Lazy<T>-Objekt nicht bei einem Zugriff eine Ausnahme auslösen und bei einem nachfolgenden Zugriff einen Wert zurückgeben.

Die Zwischenspeicherung für Ausnahmen wird aktiviert, sobald Sie einen beliebigen System.Lazy<T>-Konstruktor verwenden, der eine Initialisierungsmethode akzeptiert (valueFactory-Parameter). Beispielsweise wird sie aktiviert, wenn Sie den Lazy(T)(Func(T))-Konstruktor verwenden. Wenn der Konstruktor auch einen LazyThreadSafetyMode-Wert (mode-Parameter) erwartet, geben Sie LazyThreadSafetyMode.None oder LazyThreadSafetyMode.ExecutionAndPublication an. Durch das Angeben einer Initialisierungsmethode wird die Zwischenspeicherung für Ausnahmen für diese beiden Modi aktiviert. Die Initialisierungsmethode kann sehr einfach sein. Sie kann z. B. den Standardkonstruktor für T aufrufen: new Lazy<Contents>(() => new Contents(), mode) in C# oder New Lazy(Of Contents)(Function() New Contents()) in Visual Basic. Wenn Sie einen System.Lazy<T>-Konstruktor verwenden, der keine Initialisierungsmethode angibt, werden Ausnahmen, die vom Standardkonstruktor für T ausgelöst werden, nicht zwischengespeichert. Weitere Informationen finden Sie unter der LazyThreadSafetyMode-Enumeration.

Hinweis Hinweis

Wenn Sie ein Lazy<T>-Objekt mit dem Wert false im isThreadSafe-Konstruktorparameter oder dem Wert LazyThreadSafetyMode.None im mode-Konstruktorparameter erstellen, müssen Sie von einem einzelnen Thread aus auf das Lazy<T>-Objekt zugreifen oder eine eigene Synchronisierung bereitstellen. Dies gilt für alle Aspekte des Objekts, einschließlich der Zwischenspeicherung für Ausnahmen.

Wie im vorherigen Abschnitt erwähnt, werden Ausnahmen von Lazy<T>-Objekten unterschiedlich behandelt, die durch Angeben von LazyThreadSafetyMode.PublicationOnly erstellt wurden. In PublicationOnly können mehrere Threads um das Initialisieren der Lazy<T>-Instanz konkurrieren. In diesem Fall werden Ausnahmen nicht zwischengespeichert, und Versuche, auf die Value-Eigenschaft zugreifen, können fortgesetzt werden, bis die Initialisierung erfolgreich ist.

In der folgenden Tabelle wird die Steuerung der Zwischenspeicherung für Ausnahmen durch die Lazy<T>-Konstruktoren zusammengefasst.

Konstruktor

Threadsicherheitsmodus

Initialisierungsmethode wird verwendet

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)

Benutzerdefiniert

Nein

Nein

Lazy(T)(Func(T), LazyThreadSafetyMode)

Benutzerdefiniert

Ja

Nein, wenn der Benutzer PublicationOnly angibt, andernfalls Ja.

Um eine öffentliche Eigenschaft mit verzögerter Initialisierung zu implementieren, definieren Sie das dahinter liegende Feld 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 additonal 
            // initialization steps here.
            return new Orders(this.CustomerID);
        });
    }

    public Orders MyOrders
    {
        get
        {
            // Orders is created on first access here.
            return _orders.Value;
        }
    }
}


Die Value-Eigenschaft ist schreibgeschützt; daher besitzt die Eigenschaft, die sie verfügbar macht, keinen set-Accessor. Wenn eine von einem Lazy<T>-Objekt unterstützte Lese-/Schreibeigenschaft erforderlich ist, muss der set ein neues Lazy<T>-Objekt erstellen und diesen dem Sicherungsspeicher zuweisen. Der set-Accessor muss einen Lambda-Ausdruck erstellen, der den neuen, an den set-Accessor übergebenen Eigenschaftswert zurückgibt, und diesen Lambda-Ausdruck an den Konstruktor für das neue Lazy<T>-Objekt übergeben. Der nächste Zugriff der Value-Eigenschaft verursacht eine Initialisierung des neuen Lazy<T>, und die zugehörige Value-Eigenschaft gibt danach den neuen Wert zurück, der der Eigenschaft zugewiesen wurde. Der Grund für diese komplizierte Anordnung besteht darin, dass der in Lazy<T> integrierte Multithreadingschutz beibehalten wird. Andernfalls müssten die Eigenschaftenaccessoren den ersten von der Value-Eigenschaft zurückgegebenen Wert zwischenspeichern und nur den zwischengespeicherten Wert ändern, und Sie müssten für diesen Zweck eigenen threadsicheren Code schreiben. Aufgrund der zusätzlichen Initialisierungen, die von einer durch ein Lazy<T>-Objekt unterstützten Lese-/Schreibeigenschaft angefordert wird, ist die Leistung dann möglicherweise nicht akzeptabel. Je nach Szenario kann zudem eine zusätzliche Koordination erforderlich sein, um Racebedingungen zwischen Settern und Gettern zu vermeiden.

In einigen Multithreadszenarien möchten Sie den einzelnen Threads möglicherweise eigene private Daten zuweisen. Solche Daten werden als threadlokale Daten bezeichnet. In .NET Framework, Version 3.5 und früher, können Sie das ThreadStatic-Attribut auf eine statische Variable anwenden, um diese threadlokal zu machen. Das Verwenden des ThreadStatic-Attributs kann jedoch zu subtilen Fehlern führen. So können sogar einfache Initialisierungsanweisungen beispielsweise bewirken, dass die Variable nur auf dem ersten Thread, der darauf zugreift, initialisiert wird, wie im folgenden Beispiel gezeigt.


[ThreadStatic]
static int counter = 1;


Auf allen anderen Threads wird die Variable mit dem Standardwert (0) initialisiert. Als Alternative können Sie in .NET Framework, Version 4, mithilfe des System.Threading.ThreadLocal<T>-Typs eine instanzbasierte, threadlokale Variable erstellen, die auf allen Threads vom Action<T>-Delegaten initialisiert wird, den Sie bereitstellen. Im folgenden Beispiel wird für alle Threads, die auf counter zugreifen, der Startwert "1" angezeigt.


ThreadLocal<int> betterCounter = new ThreadLocal<int>(() => 1);


ThreadLocal<T> umschließt sein Objekt auf die gleiche Weise wie Lazy<T>, aber es bestehen die folgenden wesentlichen Unterschiede:

  • Jeder Thread initialisiert die threadlokale Variable durch Verwendung eigener privater Daten, auf die nicht von anderen Threads aus zugegriffen werden kann.

  • ThreadLocal<T>.Value ist eine Lese-/Schreibeigenschaft und kann beliebig oft geändert werden. Dies kann sich auf Ausnahmeverteilung auswirken. So kann beispielsweise ein get-Vorgang eine Ausnahme auslösen, aber der nächste kann den Wert erfolgreich initialisieren.

  • Wenn kein Initialisierungsdelegat bereitgestellt wird, initialisiert ThreadLocal<T> seinen umschlossenen Typ mit dem Standardwert des Typs. In dieser Hinsicht ist ThreadLocal<T> mit dem ThreadStaticAttribute-Attribut konsistent.

Im folgenden Beispiel wird veranschaulicht, dass jeder Thread, der auf die ThreadLocal<int>-Instanz zugreift, eine eigene eindeutige Kopie der Daten abruft.


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


Wenn Sie die Parallel.For-Methode oder die Parallel.ForEach-Methode verwenden, um Datenquellen parallel zu durchlaufen, können Sie die Überladungen verwenden, die über eine integrierte Unterstützung für threadlokale Daten verfügen. In diesen Methoden wird die Threadlokalität mit lokalen Delegaten erreicht, um Daten zu erstellen, darauf zuzugreifen und zu bereinigen. Weitere Informationen finden Sie unter Gewusst wie: Schreiben einer Parallel.For-Schleife mit thread-lokalen Variablen sowie unter Gewusst wie: Schreiben einer Parallel.ForEach-Schleife mit thread-lokalen Variablen.

In Szenarien, in denen eine große Anzahl von Objekten verzögert initialisiert werden muss, erfordert das Umschließen jedes Objekt in einem Lazy<T>-Objekt ggf. zu viel Arbeitsspeicher oder zu viele Computerressourcen. Oder es könnten strenge Anforderungen dahingehend bestehen, wie die verzögerte Initialisierung verfügbar gemacht wird. In solchen Fällen können Sie die static-Methoden (Shared in Visual Basic) der System.Threading.LazyInitializer-Klasse verwenden, um jedes Objekt verzögert zu initialisieren, ohne dass es von einer Instanz von Lazy<T> umschlossen wird.

Im folgenden Beispiel wird angenommen, dass kein ganzes Orders-Objekt von einem Lazy<T>-Objekt umschlossen wird. Stattdessen werden einzelne Order-Objekte verzögert initialisiert, wenn dies erforderlich ist.


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


Beachten Sie, dass in diesem Beispiel die Initialisierungsprozedur für jede Iteration der Schleife aufgerufen wird. In Multithreadszenarien ist der erste Thread, der die Initialisierungsprozedur aufruft, der Thread, dessen Wert für alle Threads sichtbar ist. Spätere Threads rufen auch die Initialisierungsprozedur auf, aber ihre Ergebnisse werden nicht verwendet. Wenn diese Art von potenzieller Racebedingung nicht akzeptabel ist, verwenden Sie die Überladung von LazyInitializer.EnsureInitialized, die ein boolesches Argument und ein Synchronisierungsobjekt akzeptiert.

Fanden Sie dies hilfreich?
(1500 verbleibende Zeichen)
Vielen Dank für Ihr Feedback.

Community-Beiträge

HINZUFÜGEN
Anzeigen:
© 2014 Microsoft. Alle Rechte vorbehalten.