Lazy-Instantiation mit der Lazy<T>-Klasse

An english version of this post can be found here:
http://www.just-about.net/lazy-t-english

Im Zusammenhang mit MEF bin ich auf ein Feature von .Net 4.0 gestoßen, welches mir bisher nicht bekannt war. Genauer meine ich die Klasse Lazy<T>, welche ein standardisiertes und threadsicheres Vorgehen bei Lazy Instantiation bietet.

Lazy Instantiation beschreibt eine Art der Instatierung von Objekten, die erst zu dem Zeitpunkt ausgeführt wird wenn auf jene Objekte zugegriffen wird. Ein solches Vorgehen hat den Vorteil, dass unter Umständen Ressourcen gespart werden weil aufwändige Operationen nur wirklich dann angefordert werden wenn man sie benötigt.

In der Vergangenheit wurde dafür häufig der Null-coalescing-Operator genutzt:

 
public class A 
{
     private DataClass _data;
     public DataClass Data
     {
           get
           {
                 return _data ?? (_data = new DataClass());
           }
     }
 }
 

Mit Lazy<T> sieht das Beispiel etwas anders aus. Denn hier muss man zunächst eine Instanz von Lazy erzeugen, welches sich dann um die Instanzierung der Klasse des eigentlichen Typs kümmert. Diese Erzeugung wird ausgelöst, sobald ein Zugriff auf die Value Property erfolgt.

 
public class A 
{
   private Lazy<DataClass> _data = new Lazy<DataClass>();

   public DataClass Data
   {
        get
        {
            return _data.Value;
        }
    }
}

Soweit so unspektakulär, immerhin haben wir das Problem mit der Threadsicherheit immer noch nicht gelöst. Denn im obigen Fall handelt es sich noch um eine unsichere Lösung. Um dieses Problem zu umgehen muss man dem Konstruktor der Lazy-Klasse einfach ein true übergeben. Damit werden alle Aufrufer bis auf den ersten blockiert bis die Instanz erstellt wurde.

Interessant ist dabei vor allem die interne Umsetzung. Bedient man sich des, mittlerweile kostenpflichtigen, Reflectors sieht man folgenden Code des Konstruktors:

public Lazy(bool isThreadSafe) 
 : this(isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None)
{}

Tatsächlich wird über das true also nur ein Wert der LazyThreadSafetyMode-Enumeration ausgewählt. Genau dieser Wert sagt aus, dass bei der Erstellung alle Threads zu blockieren sind. Nun kann ein solches Verhalten aber sehr schnell zu Deadlocks führen, weshalb die Enumeration noch einen weiteren Wert anbietet, der da heißt PublicationOnly.

PublicationOnlyerlaubt wiederum jedem Thread eine Instanz der Klasse zu erstellen. Wobei die Instanz die zu erst fertig gestellt wird, an alle Threads zurück gegeben wird. Auf diese Weise dreht man den Spieß also um. Denn nun müssen nicht alle Threads warten bis der erste Aufrufer fertig ist, sondern der der zu erst fertig ist stellt allen anderen den entsprechenden Wert zur Verfügung.

Diese Sicherheit vor Deadlocks wird jedoch durch einen erhöhten Ressourcenverbrauch erkauft. Immerhin wird nun Rechenzeit und Speicher an Aktionen „verschwendet“ die letztendlich kein verwertbares Resultat haben. Es wird also klar, dass man sich entscheiden muss was wichtiger ist, Geschwindigkeit oder Sicherheit.

Bevor ich nun zum Schluss komme möchte ich noch das Handling von Konstruktoren mit Parameterliste beschreiben. Die bisherigen Aktionen können nämlich nur unter der Annahme ausgeführt werden, dass eine Klasse einen parameterlosen Konstruktor besitzt. Dies dürfte jedoch eher selten der Fall sein. Aus diesem Grund bietet der Konstruktor von Lazy noch eine weitere Überladung an.

 
public class A 
{
   private Lazy<DataClass> _data;

   // Constructor
   public A(ILogger logger)
   {
        _data = new Lazy<DataClass>( () => {

              // On access create instance with logger
              return new DataClass(logger); 

        });
   }

   public DataClass Data
   {
        get
        {
            return _data.Value;
        }
    }
}

Bei diesem Konstruktor kann eine Funktion übergeben werden. Funktionen haben gegenüber den sonst von Lamda-Expressions eher bekannten Actions einen erzwungenen Rückgabewert. Dies heißt in unserem Fall, dass wir dem Konstruktor eine Factory-Methode übergeben können über welche die notwendige Instanz des zu ladenen Objekts erzeugt wird.

Genau hier besteht für mich auch der größte Vorteil von Lazy denn auf die Weise können wirklich aufwändige Aktionen threadsafe ausgeführt werden. Aktionen die so aufwändig sind, dass man sie nicht zu Beginn des Programmstarts machen möchte. Also genau der Grund weshalb man Lazy Loading/Instanziation/Initialization überhaupt einsetzt.

Kommentar hinterlassen