Implementacja Finalize oraz CriticalFinalizerObject

O destruktorach pisałem już kilka razy na blogu. W wielkim skrócie – zawsze należy przemyśleć decyzje o implementacji Finalize ponieważ wiąże to się z spadkiem wydajności (obiekt może być nawet “wypromowany” do drugiej generacji GC). Czasami jednak zachodzi taka potrzeba – głównie w przypadku użycia niezarządzanych zasobów. CriticalFinalizerObject daje nam jeszcze kilka dodatkowych gwarancji. Przed przeczytaniem tego wpisu, polecam zapoznanie się z poprzednim postem o CER.

Co zatem da nam dziedziczenie po CriticalFinalizerObject? Po kolei:

  1. Tak samo jak w przypadku CER, kod w Finalize będzie skompilowany dużo wcześniej. Daje to gwarancje, że nie wystąpi OutOfMemoryException. Pamięć jest alokowana z góry co ma pomóc w zagwarantowaniu wywołania FInalize. Pamiętajmy, że w destruktorze najprawdopodobniej umieścimy kod zwalniający zasoby niezarządzane. W przypadku gdyby CLR nie miał wystarczającej pamięci na skompilowanie i wykonanie kodu Finalize, wtedy będziemy mieć do czynienia z wyciekiem pamięci.
  2. Wszystkie destruktory obiektów, które dziedziczą po CriticalFinalizerObject, będą wywołane PO wykonaniu Finalize w obiektach niedziedziczących po CriticalFinalizerObject. Dlaczego jest to takie ważne? Dobrym przykładem jest klasa FileStream. Umożliwia ona operacje na plikach a zatem na zasobach niezarządzanych. Z tego względu posiada wskaźnik do kodu niezarządzanego (w formie SafeHandle ale o tym w następnym w wpisie).  SafeHandle dziedziczy po CriticalFinalizerObject zatem jego finalizer będzie wykonany przed destruktorem klasy FileStream, która nie dziedziczy po CrtiticalFinalizerObject. W momencie zwalniania zasobów, FileStream może dokonać operacji flush na SafeHandle, ponieważ ma pewność, że SafeHandle nie został jeszcze zwolniony. Jest to konieczne, ponieważ FileStream buforuje dane i nie wykonuje operacji bezpośrednio na plikach(ze względu na wydajność).
  3. Finalizer będzie wykonany nawet gdy AppDomain zostanie usunięta z pamięci w sposób nieoczekiwany (głównie przez środowisko uruchomieniowe).

CriticalFinalizerObject ma bardzo ważne zastosowanie w klasie SafeHandle, ale o tym w następnym w poście. W dzisiejszym wpisie chciałem tylko przybliżyć zasadę działania CriticalFinalizerObject. Korzystanie z niego jest bardzo proste, wystarczy dziedziczyć po nim tzn.:

class CerExample:CriticalFinalizerObject
{        
   ~CerExample()
   {
       Console.WriteLine("~ctor");
   }
}

One thought on “Implementacja Finalize oraz CriticalFinalizerObject”

Leave a Reply

Your email address will not be published.