LazyInitializer oraz Lazy<T>

.NET zawiera klasy przeznaczone do leniwej inicjalizacji i zwykle nie trzeba samemu implementować tego wzorca. Jeśli jakiś obiekt, chcemy inicjalizować dopiero w momencie, gdy jest on potrzebny to można skorzystać ze wspomnianych typów.

Lazy<T> to typ generyczny i wystarczy w konstruktorze przekazać metodę tworzącą obiekt:

var largeObject = new Lazy<LargeObject>(() =>
{
 Thread.Sleep(5000);
 Console.WriteLine("Inicjalizacja...");
 return new LargeObject();
});

var instance1 = largeObject.Value;
var instance2 = largeObject.Value;

W momencie pierwszego wywołania właściwości Value, obiekt zostanie utworzony, co spowoduje wyświetlenie napisu “Inicjalizacja…”. Kolejne odwołania do właściwości będą już korzystały z tej samej, zbuforowanej wartości. Możliwe jest również stworzenie obiektu Lazy, bez przekazania metody inicjalizacyjnej:

var largeObject = new Lazy<LargeObject>();

W takim przypadku, po prostu domyślny konstruktor zostanie wywołany.

Kolejnym parametrem jaki możemy przekazać w konstruktorze jest isThreadSafe:

var largeObject = new Lazy<LargeObject>(isThreadSafe:false);

Task.Factory.StartNew(() => largeObject.Value);
Task.Factory.StartNew(() => largeObject.Value);
Task.Factory.StartNew(() => largeObject.Value);

Console.ReadLine();

Jeśli isThreadSafe ustawiony jest na false, to znaczy, że instancja nie jest bezpieczna z punktu widzenia wielowątkowości. Z tego względu, konstruktor zostanie wywołany osobno w każdym z wątków. Jeśli przekażemy wartość true, wtedy obiekt będzie współdzielony przez wszystkie wątki.

Podobną funkcję pełni parametr LazyThreadSafetyMode:

var largeObject = new Lazy<LargeObject>(LazyThreadSafetyMode.ExecutionAndPublication);

LazyThreadSafetyMode posiada jedną trzech wartości. Można przekazać NONE, co powinno być wykorzystywane wyłącznie, gdy znana jest kolejność inicjalizacji. Jest to najszybszy z dostępnych trybów, ale nie gwarantuje on spójności danych – efekt jest niezdefiniowany. Z kolei ExecutionAndPublication spowoduje ten sam efekt, co isThreadSafe ustawiony na true – wyłącznie jeden obiekt zostanie stworzony. W tym przypadku blokady są wykorzystywane aby zsynchronizować wywołania. PublicationOnly pełni analogiczną funkcję do isThreadSafety false (każdy wątek tworzy własną kopię).

Kolejną klasą, przydatną w środowisku współbieżnym jest LazyInitializer. Zacznijmy od przykładu:

LargeObject largeObject = null;

LazyInitializer.EnsureInitialized(ref largeObject);
Console.WriteLine(largeObject.GetHashCode());

LazyInitializer.EnsureInitialized(ref largeObject);
Console.WriteLine(largeObject.GetHashCode());

Klasa sprawdza, czy obiekt jest już zainicjalizowany. Jeśli tak to po prostu go zwraca. W przeciwnym wypadku zostanie wywołany domyślny konstruktor. Operacja jest oczywiście thread-safe. Za pomocą LazyInitializer możemy w bezpieczny sposób zadbać, że tylko jedna instancja zostanie utworzona w środowisku wielowątkowym.

Tak jak w przypadku Lazy<T> można przekazać własną fabrykę:

LazyInitializer.EnsureInitialized(ref largeObject,()=>new LargeObject());

One thought on “LazyInitializer oraz Lazy<T>”

  1. Nie wiedziałem o istnieniu LazyInitializer. Z drugiej strony nie widzę wiele zastosowania. Pod spodem razczej korzystaja z tego samego mechanizmu, tylko Lazy ładnie to juz opakowuje. Oczywiście są sytuacje, że LazyInitializer jest szybszy, ale wychodzi to dopiero w benchmarkach (mały overhead związany z opakowaniem).

    Jako ciekawostka – spoko.

Leave a Reply

Your email address will not be published.