Czy Śmieciarz zadzwoni IDisposable.Pozbywasz się mnie?

. NET IDisposable Pattern implikuje, że jeśli napiszesz finalizer i zaimplementujesz IDisposable, Twój finalizer musi jawnie wywołać Dispose. Jest to logiczne i jest to, co zawsze robiłem w rzadkich sytuacjach, w których finalizator jest uzasadniony.

Jednak, co się stanie, jeśli po prostu to zrobię:

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

I nie wdrażaj finalizera ani niczego innego. Czy framework wywoła dla mnie metodę Dispose?

Tak zdaję sobie sprawę, że to brzmi głupio, a wszystkie logika sugeruje, że tak nie będzie, ale zawsze miałem 2 rzeczy z tyłu głowy, które sprawiały, że byłem niepewny.

  1. Ktoś kilka lat temu powiedział mi kiedyś, że w rzeczywistości tak się stanie, a ta osoba miała bardzo solidne osiągnięcia w "znajomości swoich rzeczy."

  2. Kompilator / framework robi inne "magiczne" rzeczy w zależności od implementowanych interfejsów( np.: foreach, metody rozszerzeń, serializacja na podstawie atrybutów itp.), więc ma sens, że może to być "Magia" też.

Podczas gdy czytałem wiele rzeczy na ten temat, i było wiele rzeczy sugerowane, nigdy nie byłem w stanie znaleźć ostateczne Tak lub nie ODPOWIEDŹ na to pytanie.

Author: Orion Edwards, 2008-09-05

9 answers

. Net Garbage Collector wywołuje obiekt.Finalizuj metodę obiektu przy usuwaniu śmieci. By default this does nothing and must be overdden if you want to free additional resources.

Dispose nie jest wywoływane automatycznie i musi być wywoływane explicity, jeśli zasoby mają zostać zwolnione, np. w bloku "using" lub "try finally"

Zobacz http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx więcej Informacje

 104
Author: Xian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 00:40:06

Chcę podkreślić punkt Briana w jego komentarzu, ponieważ jest on ważny.

Finalizery nie są deterministycznymi destruktorami jak w C++. Jak zauważyli inni, nie ma gwarancji, kiedy zostanie wywołana, i rzeczywiście, jeśli masz wystarczająco dużo pamięci, jeśli kiedykolwiek zostanie wywołana. Ale złe w finalizerach jest to, że, jak powiedział Brian, powoduje to, że Twój obiekt przetrwa wywóz śmieci. To może być złe. Dlaczego?

Jak możesz lub nie wiesz, GC jest podziel na generacje-Gen 0, 1 i 2, plus sterta dużych obiektów. Split jest luźnym terminem - otrzymujesz jeden blok pamięci, ale są wskaźniki miejsca, w którym obiekty Gen 0 zaczynają się i kończą.

Proces myślenia polega na tym, że prawdopodobnie użyjesz wielu przedmiotów, które będą krótkotrwałe. Powinny więc być łatwe i szybkie, aby GC mogło dotrzeć do obiektów - Gen 0. Więc kiedy jest ciśnienie pamięci, pierwszą rzeczą, którą robi, jest zbiór Gen 0.

Teraz, jeśli to nie rozwiąże wystarczającej presji, następnie wraca i wykonuje zamiatanie Gen 1 (ponowne wykonywanie gen 0), a jeśli nadal nie wystarcza, wykonuje zamiatanie Gen 2 (Ponowne wykonywanie Gen 1 i Gen 0). Tak więc czyszczenie długotrwałych obiektów może trochę potrwać i być dość kosztowne (ponieważ twoje wątki mogą zostać zawieszone podczas operacji).

Oznacza to, że jeśli zrobisz coś takiego:

~MyClass() { }

Twój obiekt, bez względu na wszystko, dożyje Generacji 2. Dzieje się tak dlatego, że GC nie ma możliwości wywołania finalizera podczas zbierania śmieci. Więc obiekty, które muszą zostać ukończone, są przenoszone do specjalnej kolejki, która ma zostać usunięta przez inny wątek (Wątek finalizera - który jeśli zabijesz, spowoduje, że wszystkie złe rzeczy się wydarzą). Oznacza to, że Twoje obiekty kręcą się dłużej i mogą wymusić większą liczbę zbiórek śmieci.

Więc, wszystko to jest tylko po to, aby wrócić do domu punkt, że chcesz użyć IDisposable do czyszczenia zasobów w miarę możliwości i poważnie spróbować znaleźć sposoby wokół korzystania z finalizer. To w Twojej aplikacji najlepiej zainteresowania.

 58
Author: Cory Foy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-18 00:12:25

Jest już dużo dobrej dyskusji, i jestem trochę spóźniony na imprezę, ale sam chciałem dodać kilka punktów.

  • Zbieracz śmieci nigdy nie wykona bezpośrednio metody utylizacji za Ciebie.
  • GC będzie uruchamiać finalizery, gdy tylko będzie to możliwe.
  • jeden wspólny wzorzec, który jest używany dla obiektów, które mają finalizer, to wywołanie metody, która jest przez Konwencję zdefiniowana jako Dispose (bool disposing) przekazująca false, aby wskazać, że wywołanie zostało wykonane z powodu finalizacji, a nie wyraźnego wywołania Dispose.
  • dzieje się tak dlatego, że nie jest bezpiecznie wprowadzać żadnych założeń dotyczących innych zarządzanych obiektów podczas finalizacji obiektu (mogły one już być finalizowane).

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

 public void Dispose() {
  Dispose(true);
 }

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

To prosta wersja, ale istnieje wiele niuansów, które mogą Cię potknąć na ten wzór.

  • Umowa o IDisposable.Dispose oznacza, że musi być bezpieczne wielokrotne wywołanie (wywołanie Dispose na obiekcie, który był już usuwany nie powinien nic robić)
  • właściwe zarządzanie hierarchią dziedziczenia obiektów jednorazowego użytku może być bardzo skomplikowane, zwłaszcza jeśli różne warstwy wprowadzają nowe zasoby jednorazowe i niezarządzane. W powyższym wzorze Dispose (bool) jest wirtualny, aby umożliwić jego nadpisanie, aby można było nim zarządzać, ale uważam, że jest podatny na błędy.

Moim zdaniem znacznie lepiej jest całkowicie unikać jakichkolwiek typów, które bezpośrednio zawierają zarówno jednorazowe odniesienia i natywnych zasobów, które mogą wymagać finalizacji. SafeHandle zapewniają bardzo czysty sposób na to poprzez enkapsulację natywnych zasobów do jednorazowego użytku, które wewnętrznie zapewniają własną finalizację (wraz z szeregiem innych korzyści, takich jak usunięcie okna podczas P/Invoke, gdzie natywny uchwyt może zostać utracony z powodu wyjątku asynchronicznego).

Po prostu zdefiniowanie SafeHandle czyni to banalnym:


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

Pozwala uprościć Typ zawierający do:


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}
 27
Author: Andrew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 11:07:14

Nie sądzę. Masz kontrolę nad tym, kiedy Dispose jest wywoływane, co oznacza, że teoretycznie możesz napisać kod dispose, który zakłada (na przykład) istnienie innych obiektów. Nie masz kontroli nad tym, kiedy finalizer jest wywoływany, więc byłoby nieciekawe, aby finalizer automatycznie wywoływał Dispose w Twoim imieniu.


EDIT: wyjechałem i testowałem, żeby się upewnić:

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred's gone, and he's not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}
 6
Author: Matt Bishop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 00:43:11

Nie w przypadku, gdy opisujesz, Ale GC zadzwoni Finalizer dla Ciebie, jeśli go masz.

Jednak. Następny garbage collection, zamiast być zbierane, obiekt przejdzie do finalizacji que, wszystko zostanie zebrane, to finalizer nazywa. Następna kolekcja po tym zostanie uwolniona.

W zależności od ciśnienia pamięci aplikacji, możesz nie mieć gc dla tego generowania obiektu przez jakiś czas. Więc w przypadku powiedzmy, strumienia plików lub połączenie db, być może będziesz musiał poczekać chwilę na uwolnienie niezarządzanego zasobu w wywołaniu finalizera, powodując pewne problemy.

 3
Author: Brian Leahy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 00:40:04

GC będzie nie wywoływać dispose. Może zadzwonić do twojego finalizatora, ale nawet to nie jest gwarantowane w każdych okolicznościach.

Zobacz ten Artykuł aby omówić najlepszy sposób radzenia sobie z tym.

 1
Author: Rob Walker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 00:42:47

Nie, to się nie nazywa.

Ale to sprawia, że łatwo nie zapomnieć o pozbyciu się przedmiotów. Wystarczy użyć słowa kluczowego using.

Wykonałem w tym celu następujący test:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }
 0
Author: penyaskito,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 00:40:12

Dokumentacja IDisposable daje dość jasne i szczegółowe wyjaśnienie zachowania, a także przykładowy kod. GC nie wywoła metody Dispose() w interfejsie, ale wywoła finalizer dla Twojego obiektu.

 0
Author: Joseph Daigle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 00:47:07

Wzorzec IDisposable został stworzony głównie do wywołania przez programistę, jeśli masz obiekt, który implementuje IDisposable programista powinien albo zaimplementować słowo kluczowe using wokół kontekstu obiektu, albo wywołać metodę Disposable bezpośrednio.

Zabezpieczenie dla wzorca polega na zaimplementowaniu finalizera wywołującego metodę Dispose (). Jeśli tego nie zrobisz, możesz utworzyć jakieś wycieki pamięci tzn.: jeśli stworzysz jakiś wrapper COM i nigdy nie wywołasz System.Runtime.Interop.Marshall.ReleaseComObject(comObject) (który zostanie umieszczony w metodzie Dispose).

Nie ma magii w clr, aby wywoływać metody usuwania automatycznie inne niż śledzenie obiektów, które zawierają finalizery i przechowywanie ich w tabeli Finalizerów przez GC i wywoływanie ich, gdy jakaś heurystyka sprzątania zacznie działać przez GC.

 0
Author: Erick Sgarbi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 07:31:24