Celem Dispose jest uwolnienie niezarządzanych zasobów. W pewnym momencie należy to zrobić, w przeciwnym razie nigdy nie zostaną oczyszczone. Śmieciarka nie wie, jak wywołać DeleteHandle()
zmienną typu IntPtr
, nie wie, czy musi zadzwonić DeleteHandle()
.
Uwaga : Co to jest niezarządzany zasób ? Jeśli znalazłeś go w Microsoft .NET Framework: jest zarządzany. Jeśli sam zacząłeś grzebać w MSDN, jest to niezarządzane. Wszystko, czego używałeś wywołań P / Invoke, aby wyjść poza przyjemny, wygodny świat wszystkiego, co jest dostępne w .NET Framework, jest niezarządzane - a teraz jesteś odpowiedzialny za oczyszczenie go.
Obiekt, który utworzyłeś, musi ujawnić jakąś metodę, którą świat zewnętrzny może wywołać, aby oczyścić niezarządzane zasoby. Metodę można nazwać dowolnie:
public void Cleanup()
lub
public void Shutdown()
Zamiast tego istnieje znormalizowana nazwa tej metody:
public void Dispose()
Utworzono nawet interfejs IDisposable
, który ma tylko jedną metodę:
public interface IDisposable
{
void Dispose()
}
Sprawiasz, że obiekt ujawnia IDisposable
interfejs, i w ten sposób obiecujesz, że napisałeś tę pojedynczą metodę, aby oczyścić niezarządzane zasoby:
public void Dispose()
{
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
I jesteś skończony. Tyle że możesz zrobić lepiej.
Co się stanie, jeśli twój obiekt przydzieli 250 MB System.Drawing.Bitmap (tj. Zarządzaną klasą .NET Bitmap) jako pewnego rodzaju bufor ramki? Jasne, jest to zarządzany obiekt .NET, a śmieciarz go zwolni. Ale czy naprawdę chcesz zostawić 250 MB pamięci po prostu tam siedząc - czekając, aż śmieciarz w końcu przyjdzie i ją zwolni? Co się stanie, jeśli istnieje otwarte połączenie z bazą danych ? Z pewnością nie chcemy, aby to połączenie było otwarte i czekało, aż GC sfinalizuje obiekt.
Jeśli użytkownik zadzwonił Dispose()
(co oznacza, że nie planuje już używać obiektu), dlaczego nie pozbyć się marnotrawstwa map bitowych i połączeń z bazą danych?
Więc teraz będziemy:
- pozbyć się niezarządzanych zasobów (ponieważ musimy), i
- pozbyć się zarządzanych zasobów (ponieważ chcemy być pomocni)
Zaktualizujmy więc naszą Dispose()
metodę, aby pozbyć się zarządzanych obiektów:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
I wszystko jest dobrze, tyle że możesz zrobić lepiej !
Co się stanie, jeśli osoba zapomni zadzwonić Dispose()
na Twój obiekt? Wtedy wyciekną niektóre niezarządzane zasoby!
Uwaga: nie będą przeciekać zarządzanych zasobów, ponieważ w końcu uruchomi się moduł wyrzucania elementów bezużytecznych w wątku w tle i zwolnią pamięć związaną z wszelkimi nieużywanymi obiektami. Będzie to obejmowało Twój obiekt i wszelkie zarządzane obiekty, których używasz (np. Bitmap
I DbConnection
).
Jeśli osoba zapomniała zadzwonić Dispose()
, nadal możemy uratować jej bekon! Mamy jeszcze sposób nazwać to dla nich kiedy garbage collector wreszcie trafia dokoła do uwolnienia (tj finalizowanie) Naszym celem.
Uwaga: Śmieciarka ostatecznie uwolni wszystkie zarządzane obiekty. Kiedy to robi, wywołuje Finalize
metodę na obiekcie. GC nie wie ani nie dba o twoją metodę Dispose . To była tylko nazwa, którą wybraliśmy dla metody, którą nazywamy, gdy chcemy pozbyć się niezarządzanych rzeczy.
Zniszczenie naszego obiektu przez śmieciarza to idealny czas na uwolnienie tych nieznośnych niezarządzanych zasobów. Robimy to, zastępując Finalize()
metodę.
Uwaga: W języku C # nie jawnie zastępujesz Finalize()
metody. Napisać metodę, która wygląda jak danego C ++ destruktora i kompilatora zatrzymuje się swoją wdrożenie Finalize()
metody:
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
Ale w tym kodzie jest błąd. Widzisz, śmieciarz działa na wątku w tle ; nie znasz kolejności niszczenia dwóch obiektów. Jest całkiem możliwe, że w twoim Dispose()
kodzie nie ma już obiektu zarządzanego , którego próbujesz się pozbyć (ponieważ chciałeś być pomocny):
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
this.frameBufferImage = null;
}
}
Potrzebny jest zatem sposób, Finalize()
aby powiedzieć, Dispose()
że nie powinien on dotykać żadnych zarządzanych zasobów (ponieważ mogą ich nie być już ), jednocześnie uwalniając niezarządzane zasoby.
Standardowym wzorcem do tego jest posiadanie Finalize()
i Dispose()
wywoływanie trzeciej (!) Metody; gdzie przekazujesz logiczne powiedzenie, jeśli dzwonisz z Dispose()
(w przeciwieństwie do Finalize()
), co oznacza, że można bezpiecznie uwolnić zarządzane zasoby.
Ta wewnętrzna metoda może mieć dowolną nazwę, taką jak „CoreDispose” lub „MyInternalDispose”, ale tradycja nazywa ją Dispose(Boolean)
:
protected void Dispose(Boolean disposing)
Jednak bardziej pomocną nazwą parametru może być:
protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too, but only if I'm being called from Dispose
//(If I'm being called from Finalize then the objects might not exist
//anymore
if (itIsSafeToAlsoFreeManagedObjects)
{
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
}
I zmieniasz implementację IDisposable.Dispose()
metody na:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
a twój finalizator:
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
Uwaga : Jeśli Twój obiekt pochodzi z obiektu, który implementuje Dispose
, nie zapomnij wywołać swojej podstawowej metody Dispose po zastąpieniu Dispose:
public override void Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
I wszystko jest dobrze, tyle że możesz zrobić lepiej !
Jeśli użytkownik wywoła Dispose()
Twój obiekt, wszystko zostało wyczyszczone. Później, kiedy pojawia się moduł wyrzucający śmieci i wywołuje Finalize, zadzwoni Dispose
ponownie.
Jest to nie tylko marnotrawstwo, ale jeśli Twój obiekt zawiera śmieciowe odniesienia do obiektów, które zostały już usunięte po ostatnim wywołaniu Dispose()
, spróbujesz usunąć je ponownie!
Zauważysz w moim kodzie, że starałem się usunąć odwołania do obiektów, które pozbyłem się, więc nie próbuję wywoływać Dispose
do śmieci. Ale to nie powstrzymało przedostania się subtelnego robaka.
Gdy użytkownik wywołuje Dispose()
: uchwyt CursorFileBitmapIconServiceHandle jest zniszczony. Później, gdy moduł wyrzucający śmieci uruchomi się, spróbuje ponownie zniszczyć ten sam uchwyt.
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy
...
}
Sposób, w jaki to naprawiasz, polega na powiedzeniu śmieciarzowi, że nie musi zawracać sobie głowy finalizowaniem obiektu - jego zasoby zostały już wyczyszczone i nie trzeba więcej pracować. Można to zrobić poprzez wywołanie GC.SuppressFinalize()
w Dispose()
metodzie:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}
Teraz, gdy użytkownik zadzwonił Dispose()
, mamy:
- uwolniono niezarządzane zasoby
- uwolnione zasoby zarządzane
Nie ma sensu, aby GC obsługiwał finalizator - wszystko załatwione.
Czy nie mogę użyć Finalize do czyszczenia niezarządzanych zasobów?
Dokumentacja Object.Finalize
mówi:
Metoda Finalize służy do wykonywania operacji czyszczenia niezarządzanych zasobów przechowywanych przez bieżący obiekt przed zniszczeniem obiektu.
Ale dokumentacja MSDN mówi również IDisposable.Dispose
:
Wykonuje zadania zdefiniowane przez aplikację związane z uwalnianiem, zwalnianiem lub resetowaniem niezarządzanych zasobów.
Więc co to jest? Które jest miejscem, w którym mogę uporządkować niezarządzane zasoby? Odpowiedź to:
To Twój wybór! Ale wybierz Dispose
.
Z pewnością możesz umieścić swoje niezarządzane porządki w finalizatorze:
~MyObject()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//A C# destructor automatically calls the destructor of its base class.
}
Problem polega na tym, że nie masz pojęcia, kiedy śmieciarz podejdzie do sfinalizowania obiektu. Twoje un zarządzane, UN, UN potrzebne wykorzystywane rodzime zasoby będą trzymać się aż do garbage collector ostatecznie skończy. Następnie wywoła metodę finalizatora; czyszczenie niezarządzanych zasobów. Dokumentacja Object.Finalize wskazuje na to:
Dokładny czas wykonania finalizatora jest nieokreślony. Aby zapewnić deterministyczne uwalnianie zasobów dla instancji klasy, zaimplementuj metodę Close lub zapewnij IDisposable.Dispose
implementację.
Jest to zaleta używania Dispose
do czyszczenia niezarządzanych zasobów; poznajesz i kontrolujesz, kiedy niezarządzane zasoby są usuwane. Ich zniszczenie jest „deterministyczne” .
Aby odpowiedzieć na twoje pierwotne pytanie: dlaczego nie zwolnić pamięci teraz, zamiast na to, kiedy GC zdecyduje się to zrobić? Mam twarzy oprogramowania do rozpoznawania, że potrzeby , aby pozbyć się z 530 MB wewnętrznej obrazami teraz , ponieważ nie jesteś już potrzebny. Kiedy tego nie robimy: maszyna zgrzyta do zatrzymania.
Czytanie bonusowe
Dla każdego, kto lubi styl tej odpowiedzi (wyjaśniając dlaczego , więc jak staje się oczywiste), sugeruję przeczytanie pierwszego rozdziału Essential COM Dona Boxa:
Na 35 stronach wyjaśnia problemy związane z używaniem obiektów binarnych i wymyśla COM na twoich oczach. Kiedy zrozumiesz, dlaczego COM, pozostałe 300 stron jest oczywistych i opisuje szczegółowo implementację Microsoftu.
Myślę, że każdy programista, który kiedykolwiek miał do czynienia z obiektami lub COM, powinien przynajmniej przeczytać pierwszy rozdział. To najlepsze wytłumaczenie czegokolwiek w historii.
Dodatkowe czytanie bonusowe
Kiedy wszystko, co wiesz, jest złe przez Erica Lipperta
Dlatego naprawdę bardzo trudno jest napisać poprawny finalizator, a najlepszą radą, jaką mogę ci dać, jest nie próbować .