2010-09-27 14 views
7

Mam kilka podstawowych pytań dotyczących deseniu usuwania w języku C#.Szczegółowe pytania dotyczące deseniu C# Dispose

W poniższym fragmencie kodu, który wydaje się być standardowym sposobem implementacji wzorca rozporządzania, można zauważyć, że zarządzane zasoby nie są obsługiwane, jeśli dysponowanie jest fałszywe. Jak/kiedy są obsługiwane? Czy GC przyjdzie i zajmie się zarządzanymi zasobami później? Ale jeśli tak jest, co robi wywołanie GG.SuppressFinalize (this)? Czy ktoś może podać mi przykład dysponowania zarządzanymi zasobami? Przypominają się zdarzenia związane z odhaczeniem. Coś jeszcze? Sposób, w jaki wzór jest napisany, wydaje się, że zostaną one usunięte (później), jeśli nic nie zrobisz w sekcji "Jeśli (utylizacja)". Komentarze?

protected virtual void Dispose(bool disposing) 
{ 
    if (!disposed) 
    { 
     if (disposing) 
     { 
      // Dispose managed resources. 
     } 

     // There are no unmanaged resources to release, but 
     // if we add them, they need to be released here. 
    } 
    disposed = true; 

    // If it is available, make the call to the 
    // base class's Dispose(Boolean) method 
    base.Dispose(disposing); 
} 
// implements IDisposable 
public void Dispose() 
{ 
    Dispose(true); 
    GC.SuppressFinalize(this); 
} 

Czy to prawda, co czytałem o zamki w Dispose (bool) w tym wątku, How do I implement the dispose pattern in c# when wrapping an Interop COM Object?? Mówi: "Meta-meta komentarz - a także, że ważne jest, aby nigdy nie nabywać zamków ani nie używać blokady podczas nieuporządkowanego czyszczenia." Dlaczego tak jest? Czy dotyczy to również zasobów niezarządzanych?

Wreszcie, czy kiedykolwiek zaimplementujesz finalizator (~ MyClass() w C#) bez implementacji IDisposable? Sądzę, że czytałem gdzieś, że finalizatory i IDisposable nie są konieczne (lub pożądane), jeśli nie ma żadnych niezarządzanych zasobów. Jednak widzę zastosowanie finalizatora bez IDisposable w niektórych przykładach (patrz: http://www.codeproject.com/KB/cs/idisposable.aspx jako przykład) Dzięki, Dave

+0

Dzięki za wszystkie świetne odpowiedzi wszystkim! Mogę tylko oznaczyć odpowiedź jako niestety. – Dave

Odpowiedz

5

Ten sposób wykonania wzoru IDisposable jest awaryjne sposób: W przypadku, gdy klient zapomina zadzwonić pod numer Dispose, finalizator wywoływany przez środowisko wykonawcze będzie później wywoływać Dispose(false) (zwróć uwagę, że tej części brakuje w twojej próbce).

W tym drugim przypadku, tj. Po wywołaniu przez koordynatora nazwy Dispose, zarządzane zasoby zostaną już oczyszczone, ponieważ w przeciwnym razie przedmiot, o którym mowa, nie kwalifikowałby się do czyszczenia pamięci.

Ale jeśli tak jest, co robi wywołanie GC.SuppressFinalize (this)?

Uruchamianie finalizatora wiąże się z dodatkowymi kosztami. Dlatego powinno się go unikać, jeśli to możliwe. Wywołanie GC.SuppressFinalize(this) spowoduje pominięcie działania finalizatora, a zatem obiekt może być bardziej efektywnie usuwany z pamięci.

Generalnie należy unikać polegania na finalizatorach, ponieważ nie ma gwarancji, że finalizator zostanie uruchomiony. Niektóre z problemów z finalizatorów są opisane przez Raymond Chen w następnym poście:

When do I need to use GC.KeepAlive?

+2

Mała nitpick: * "W tym drugim przypadku ... zarządzane zasoby zostaną już oczyszczone" *. Niekoniecznie tak jest: GC jest niedeterministyczna, więc może zostaną oczyszczeni, może nie. Tak czy inaczej, powinieneś zachowywać się * tak, jakby * zostały wyczyszczone (tzn. Nie powinieneś próbować z nimi nic robić). – LukeH

2

... zauważysz, że udało zasoby nie są obsługiwane, jeśli zbycie jest fałszywe. Jak/kiedy są obsługiwane?

Nie uwzględniłeś go w swojej próbce, ale często typ będzie miał destruktor, który zadzwoni pod numer Dispose(false). Tak więc, gdy disposing jest false, to „wie”, że jesteś w rozmowie finalizatora, a tym samym powinny * nie * dostęp do dowolnego zarządzanych zasobów, ponieważ mogliby mieć już zostały sfinalizowane.

procesie finalizacji GC zapewnia jedynie, że finalizatory są wywoływane, a nie zamówienie że są one wywoływane w.

co robi GG.SuppressFinalize (this) nazywamy zrobić?

Zapobiega GC z dodawania przedmiotu do kolejki finalizacji i ostatecznie wywołanie object.Finalize() (to jest swoją destructor). To optymalizacja wydajności, nic więcej.

Sposób wzór jest napisane, wydaje dostaną usuwane (później), jeśli nie zrobił nic w sekcji „if (rozstrzygających)”

Może; zależy to od tego, jak napisany jest typ. Ważnym punktem w idiomie IDisposable jest określenie "deterministic finalizaion" - mówiąc: "Chcę, aby twoje zasoby zostały uwolnione teraz" i mające na myśli coś. Jeśli „ignoruj” blok disposing=true a nie „do przodu” THE Dispose() wezwanie, jedna z dwóch rzeczy się wydarzy:

  1. Jeśli typ ma finalizatora, Finalizer dla obiektu może ostatecznie być powoływane kiedyś " później".
  2. Jeśli typ nie ma finalizatora, zarządzany zasób "wycieknie", ponieważ Dispose() nigdy nie zostanie wywołany.

ważne jest, że nigdy nie nabyć zamki lub zablokowanie użycia podczas niekontrolowana porządki „. Dlaczego tak jest? Czy dotyczy to również zasobów niezarządzanych?

To kwestia zdrowia psychicznego - TWOJE zdrowie psychiczne. Im prostszy kod, tym lepiej, i zawsze jest to dobry pomysł na nie zgłaszać wyjątków od wyjątków od Dispose(). Używanie zamków może prowadzić do wyjątków lub zakleszczeń, z których każda jest dobrym sposobem na zrujnowanie dnia. :-)

ma on kiedykolwiek zaimplementować finalizatora (~ MyClass() w C#) bez realizacji IDisposable

Jeden może, ale byłoby uznać za zły styl.

1

Normalnym sposobem usuwania obiektów jest wywołanie metody Dispose(). Po wykonaniu tej czynności wywołanie SuppressFinalize usuwa obiekt z kolejki finalizatora, przekształcając go w zwykły obiekt zarządzany, który może łatwo zostać zbuforowany.

Finalizator jest używany tylko wtedy, gdy kod nie udaje się prawidłowo usunąć obiektu. Następnie finalizator wywołuje Dispose(false), aby obiekt mógł co najmniej spróbować wyczyścić niezarządzane zasoby. Jak każdy zarządzany obiekt, do którego odniesienia obiektów mogły już zostać zebrane na tym etapie, obiekt nie powinien próbować ich usuwać.

0

Zamiast próbować poznać dyspozycję za pośrednictwem wzorca, można odwrócić sytuację i spróbować dowiedzieć się, dlaczego wzorzec jest zaimplementowany w ten sposób w oparciu o podstawy CLR i zamierzone użycie interfejsu IDisposable. Jest bardzo dobry wstęp do tego, który powinien odpowiedzieć na wszystkie twoje pytania (i kilka, o których nie myślałeś, że zapytasz) pod adresem http://msdn.microsoft.com/en-us/magazine/cc163392.aspx.

3

Opisany powyżej schemat dotyczył wymownego rozwiązania problemu nakładania się i finalizacji.

Kiedy pozbywasz chcemy:

  1. Usunąć wszystkie przedmioty jednorazowego użytku członków.
  2. Usuń obiekt bazowy.
  3. Zwolnij zasoby niezarządzane.

Podczas finalizowania chcemy:

  1. wydaniu niezarządzani zasobów.

Dodano do tego są następujące kwestie:

  1. Utylizacja powinna być bezpieczna zadzwonić kilka razy. Nie powinno być błędu, aby wywołać x.Dispose();x.Dispose();
  2. Finalizacja dodaje obciążenie do czyszczenia pamięci. Jeśli unikniemy tego, jeśli to możliwe, szczególnie jeśli już wydaliśmy zasoby niezarządzane, chcemy zablokować finalizację, ponieważ nie jest już potrzebna.
  3. Uzyskiwanie dostępu do sfinalizowanych obiektów jest obciążone. Jeśli obiekt jest finalizowany, to każdy z możliwych do skasowania członków (który również miałby do czynienia z tymi samymi problemami, co nasza klasa) może lub nie został już sfinalizowany i na pewno znajdzie się w kolejce finalizacji. Ponieważ obiekty te będą prawdopodobnie również zarządzane obiektami jednorazowymi, a ponieważ ich usunięcie spowoduje uwolnienie ich zasobów niezarządzanych, nie chcemy ich w takim przypadku usuwać.

Kod dajesz będzie (po dodaniu w finaliser że nazywa Dispose(false) zarządzać te obawy. W przypadku Dispose() nazywany będzie oczyścić oba elementy zarządzane i niezarządzane i tłumić finalizacji, a także ochronę przed wielokrotności dzwoni (w tym wypadku nie jest bezpieczny dla wątków), w przypadku wywoływania finalizatora wyczyści niezarządzanych członków,

Jednak ten wzorzec jest wymagany tylko przez wzorzec przeciwdziałania łączeniu Znacznie lepszym podejściem jest obsłużenie wszystkich niezarządzanych zasobów przez klasę, która zajmuje się tylko tym zasobem, czy SafeHandle, czy osobną klasą Twój własny. Wtedy będziesz miał jeden z dwóch wzorów, z których ten ostatni będzie rzadki:

public class HasManagedMembers : IDisposable 
{ 
    /* more stuff here */ 
    public void Dispose() 
    { 
     //if really necessary, block multiple calls by storing a boolean, but generally this won't be needed. 
     someMember.Dispose(); /*etc.*/ 
    } 
} 

To nie ma finalizatora i nie jest potrzebne.

public class HasUnmanagedResource : IDisposable 
{ 
    IntPtr _someRawHandle; 
    /* real code using _someRawHandle*/ 
    private void CleanUp() 
    { 
    /* code to clean up the handle */ 
    } 
    public void Dispose() 
    { 
    CleanUp(); 
    GC.SuppressFinalize(this); 
    } 
    ~HasUnmanagedResource() 
    { 
    CleanUp(); 
    } 
} 

ta wersja, która będzie znacznie rzadsze (nawet nie dzieje się w większości projektów) ma umowę zbycia wyłącznie do czynienia z jedynym niezarządzanego zasobu, dla których klasa jest opakowaniem, a finaliser robi to samo jeśli nie doszło do zbycia.

Ponieważ SafeHandle pozwala na obsługę drugiego wzoru, nie powinno go w ogóle potrzebować. W każdym przypadku pierwszy przykład, który podaję, zajmie się ogromną większością przypadków, w których trzeba wdrożyć IDisposable. Wzorzec podany w przykładzie powinien być używany tylko w celu zapewnienia kompatybilności wstecznej, na przykład w przypadku korzystania z klasy, która go używa.

0

Jeśli twoja klasa będzie utrzymywała zasoby niezarządzane bezpośrednio, lub jeśli kiedykolwiek może zostać odziedziczona przez klasę potomków, która to zrobi, struktura dyspozycji firmy Microsoft będzie dobrym sposobem na powiązanie finalizatora i dysponenta. Jeśli nie ma realistycznej możliwości, że albo twoja klasa lub jej potomkowie kiedykolwiek będą trzymali zasoby niezarządzane bezpośrednio, powinieneś usunąć kod szablonu i po prostu wdrożyć Dyspozycję bezpośrednio. Biorąc pod uwagę, że firma Microsoft zdecydowanie zalecała, aby zasoby niezarządzane były pakowane w klasy, których jedynym celem jest ich przechowywanie (*) (i ma klasy takie jak SafeHandle w tym celu), kod szablonu nie jest już potrzebny.

(*) Usuwanie śmieci w .net jest wieloetapowym procesem; najpierw system określa, które obiekty nie są nigdzie w bazie; następnie tworzy listę obiektów Finalize, które nie są nigdzie odsyłane. Lista i wszystkie znajdujące się na niej przedmioty zostaną ponownie zadeklarowane jako "na żywo", co oznacza, że ​​wszystkie obiekty, do których się odnoszą, również będą na żywo. W tym momencie system wykona rzeczywiste usuwanie śmieci; następnie uruchomi wszystkie finalizatory na liście. Jeśli obiekt zawiera, np. bezpośredni uchwyt do zasobu fontów (niezarządzanego), jak również odniesienia do dziesięciu innych obiektów, które z kolei posiadają bezpośrednie lub pośrednie odniesienia do setki innych obiektów, a następnie z powodu niezarządzanego zasobu obiekt będzie potrzebował finalizatora. Gdy obiekt ma zostać pobrany, ani go ani 100+ obiektów, do których ma bezpośrednie lub pośrednie referencje, będzie się kwalifikować do gromadzenia do momentu przejścia po uruchomieniu finalizatora.

Jeśli zamiast trzymać bezpośredni uchwyt do zasobu czcionki, obiekt zatrzymał odniesienie do obiektu, który przechowuje zasób fontu (i nic więcej), ten ostatni obiekt musiałby mieć finalizator, ale poprzedni nie (ponieważ nie zawiera odniesienia do niezarządzanego zasobu, tylko jeden obiekt (ten, który posiadał finalizator), a nie 100+, musiałby przetrwać pierwszy odbiór śmieci:

5

Nikt nie dostał się do ostatnie dwa pytania (btw: pytaj tylko o jeden wątek) Użycie blokady w Dispose() jest dość zabójcze dla wątku finalizatora.Nie ma górnej granicy, jak długo blokada może być trzymana, twój program zawiesi się po dwóch sekundach, gdy CLR zauważa, że ​​wątek finalizatora utknął. Co więcej, to tylko błąd. Nigdy nie należy wywoływać funkcji Dispose(), gdy inny wątek nadal może mieć odniesienie do obiektu.

Tak, wdrażanie finalizatora bez implementacji IDisposable nie jest niespotykane. Każde opakowanie obiektu COM (RCW) to robi. Podobnie jak klasa Thread. Stało się tak, ponieważ wywoływanie Dispose() nie jest praktyczne. W przypadku opakowania COM, ponieważ niemożliwe jest śledzenie wszystkich liczników referencji. W przypadku wątku, ponieważ konieczność łączenia() wątku, tak aby można było wywołać Dispose(), pokonuje cel posiadania wątku.

Zwróć uwagę na posta Jona Hanna. Wdrożenie własnego finalizatora rzeczywiście jest nieprawidłowe 99,99% czasu. Masz klasy SafeHandle do pakowania niezarządzanych zasobów. Będziesz potrzebował czegoś mało znanego, aby nie można ich było spakować.

+0

Zarządzane wątki są zawieszane podczas zbierania śmieci, ale nie sądzę, że są zawieszone podczas finalizacji. Sposób finalizacji działa, obiekty, dla których włączona jest finalizacja, oraz wszelkie obiekty, do których się odnoszą bezpośrednio lub pośrednio, nie będą zbierane podczas zbierania śmieci, ale jeśli w inny sposób będą się kwalifikować do zbierania śmieci, zostaną sfinalizowane, gdy bieżące gromadzenie śmieci przejście zostało zakończone. Po uruchomieniu finalizatorów, chyba że obiekty zostaną ponownie zarejestrowane w celu sfinalizowania, nie będą już kwalifikować się do finalizacji i zostaną przeniesione na następny gc. – supercat

+0

@super - masz rację, nie wiesz, dlaczego to napisałem. Zniszczone obrażenia, dzięki. –

+0

@Hans: Myślę, że klasa Thread używa finalizatorów do czyszczenia alokacji identyfikatorów zarządzanych wątków. Każdy utworzony osobno obiekt Thread musi mieć odrębny identyfikator; ponieważ identyfikatory wątków mają tylko 32 bity, szkielet musi być zdolny do ich ponownego użycia (albo w przeciwnym razie straciłby każdy program, który wytworzyłby dwa miliardy obiektów wątku w trakcie jego trwania). Ponieważ złe rzeczy mogą się zdarzyć, jeśli dwa obiekty Thread mają ten sam obiekt ManagedThreadID, finalizatory są używane w celu zapewnienia, że ​​identyfikatory są udostępniane do ponownego użycia, gdy nie ma już żadnych odniesień do obiektów Thread, które je przechowują. – supercat