2010-09-27 8 views
7

Ich habe ein paar grundlegende Fragen zum Dispose Pattern in C#.Spezifische Fragen zu C# Dispose Pattern

Im folgenden Codefragment, das eine Standardmethode zum Implementieren des Dispose-Musters zu sein scheint, werden Sie feststellen, dass verwaltete Ressourcen nicht behandelt werden, wenn disposing false ist. Wie/wann werden sie behandelt? Kommt der GC mit und verwaltet später die verwalteten Ressourcen? Aber wenn das der Fall ist, was macht der Aufruf GG.SuppressFinalize (this)? Kann mir jemand ein Beispiel für die Entsorgung verwalteter Ressourcen nennen? Unhooking-Ereignisse kommen mir in den Sinn. Noch etwas? Die Art und Weise, wie das Muster geschrieben wird, scheint so, als würden sie (später) entsorgt werden, wenn Sie nichts im Abschnitt "if (disposing)" getan haben. Bemerkungen?

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); 
} 

Ist es wahr, was ich über Schleusen in Dispose (bool) in diesem Thread gelesen, How do I implement the dispose pattern in c# when wrapping an Interop COM Object?? Es sagt, "Meta-Meta-Kommentar - sowie das, es ist wichtig, dass Sie nie Sperren erwerben oder sperren während Ihrer nicht verwalteten Bereinigung." Warum ist das? Gilt es auch für nicht verwaltete Ressourcen?

Schließlich implementiert jemals einen Finalizer (~ MyClass() in C#), ohne IDisposable zu implementieren? Ich glaube, ich habe irgendwo gelesen, dass Finalizer und IDisposable nicht notwendig (oder wünschenswert) sind, wenn es keine nicht verwalteten Ressourcen gibt. Ich habe jedoch die Verwendung eines Finalizerthread ohne IDisposable in einigen Beispielen sehen (siehe: http://www.codeproject.com/KB/cs/idisposable.aspx als ein Beispiel) Danke, Dave

+0

Danke für all die tollen Antworten jeder! Ich kann leider nur eine als Antwort markieren. – Dave

Antwort

5

Diese Art der Durchführung des IDisposable Muster wird eine ausfallsichere Art und Weise: Falls ein Client vergisst, Dispose aufzurufen, ruft der von der Laufzeit aufgerufene Finalizer später Dispose(false) auf (Beachten Sie, dass dieser Teil in Ihrem Beispiel fehlt). Im letzteren Fall, d. H. Wenn Dispose vom Finalizer aufgerufen wird, sind verwaltete Ressourcen bereits bereinigt worden, da andernfalls das fragliche Objekt nicht für die Speicherbereinigung geeignet gewesen wäre.

Aber wenn das der Fall ist, was macht der Aufruf von GC.SuppressFinalize (this)?

Das Ausführen des Finalizers ist mit zusätzlichen Kosten verbunden. Daher sollte es möglichst vermieden werden. Durch den Aufruf von GC.SuppressFinalize(this) wird das Ausführen des Finalizers übersprungen und das Objekt kann daher effizienter mit Garbage Collections erfasst werden.

Im Allgemeinen sollte man sich nicht auf Finalizer verlassen, da es keine Garantie gibt, dass ein Finalizer läuft. Einige der Probleme, mit Finalizers werden von Raymond Chen in dem folgenden Beitrag beschrieben:

When do I need to use GC.KeepAlive?

+2

Ein kleiner Nitpick: * "Im letzteren Fall ... wurden die verwalteten Ressourcen bereits bereinigt" *. Das ist nicht unbedingt der Fall: GC ist nicht deterministisch, also wurden sie vielleicht aufgeräumt, vielleicht auch nicht. In jedem Fall sollten Sie sich verhalten *, als ob * sie gereinigt wurden (dh, Sie sollten nicht versuchen, etwas mit ihnen zu tun). – LukeH

2

... werden Sie feststellen, dass verwalteten Ressourcen feststellen, werden nicht behandelt, wenn Entsorgung falsch ist. Wie/wann werden sie behandelt?

Sie haben es nicht in Ihre Beispiel aufgenommen, aber oft wird der Typ einen Destruktor haben, der Dispose(false) aufrufen wird. Wenn also disposingfalse ist, Sie „wissen“, dass Sie in einem Finalizerthread Gespräch führen und damit sollte * nicht * Zugriff auf alle verwalteten Ressourcen, weil könnten sie haben bereits abgeschlossen.

Die GC Finalisierung stellt nur sicher, dass Finalizer aufgerufen wird, nicht die um, dass sie in aufgerufen sind.

etwas macht die GG.SuppressFinalize (this) nennen tun?

Es verhindert, dass der GC von Ihrem Objekt in die Finalisierungsschlange Hinzufügen und schließlich object.Finalize() (das heißt Ihre destructor) aufrufen. Es ist eine Leistungsoptimierung, mehr nicht.

Die Art und Weise das Muster geschrieben ist, scheint es, sie (später) erhalten entsorgt würde, wenn Sie nichts in der „if (disposing)“ Abschnitt

Vielleicht tat; Es hängt davon ab, wie der Typ geschrieben ist. Ein wichtiger Punkt zum IDisposable Idiom ist für "deterministic finalizaion" - sagen "Ich möchte Ihre Ressourcen befreit jetzt" und es etwas bedeuten. Wenn Sie auf „Ignorieren“ den disposing=true Block und nicht „vorwärts“ die Dispose() Anruf, ein von zwei Dingen passieren:

  1. Wenn der Typ einen Finalizer hat, irgendwann die Finalizerthread für das Objekt schließlich geltend gemacht werden kann " später".
  2. Wenn der Typ keinen Finalizer hat, wird die verwaltete Ressource "leak", da Dispose() nie auf ihnen aufgerufen wird.

ist es wichtig, dass Sie nie während der unmanaged Bereinigung Schlösser oder Verwendung Verriegelung erwerben.“Warum ist das so? Gilt es auch für nicht verwaltete Ressourcen?

Es ist ein geistiges Problem - Ihre Gesundheit. Je einfacher Ihr Cleanup-Code, desto besser und es ist immer eine gute Idee zu nicht werfen Ausnahmen von Dispose(). Die Verwendung von Sperren kann zu Ausnahmebedingungen oder Deadlocks führen, von denen beide eine gute Möglichkeit darstellen, Ihren Tag zu ruinieren. :-)

sich auf je einen Finalizer (~ MyClass() in C#) implementieren, ohne IDisposable Umsetzung

Man könnte, aber es wäre schlechter Stil betrachtet werden.

1

Der normale Weg für Objekte, die entsorgt werden müssen, ist der Aufruf der Methode Dispose(). Auf diese Weise entfernt der Aufruf SuppressFinalize das Objekt aus der Finalizer-Warteschlange und wandelt es in ein regulär verwaltetes Objekt um, das leicht in den Garbage Collection-Modus versetzt werden kann.

Der Finalizer wird nur verwendet, wenn der Code das Objekt nicht ordnungsgemäß entsorgen kann. Dann ruft der Finalizer Dispose(false) auf, damit das Objekt zumindest versuchen kann, nicht verwaltete Ressourcen zu bereinigen. Da alle verwalteten Objekte, auf die das Objekt verweist, in diesem Stadium möglicherweise bereits zu Garbage Collection gesammelt wurden, sollte das Objekt nicht versuchen, sie zu bereinigen.

0

Anstatt zu versuchen, die Disposition über das Muster zu erlernen, möchten Sie vielleicht etwas umdrehen und herausfinden, warum das Muster auf der Grundlage der CLR-Grundlagen und der beabsichtigten Verwendung der IDisposable-Schnittstelle auf diese Weise implementiert wird. Es gibt eine sehr gute Einführung, die alle Ihre Fragen beantworten sollte (und ein paar, die Sie nicht denken wollten) unter http://msdn.microsoft.com/en-us/magazine/cc163392.aspx.

3

Das oben beschriebene Muster war eine Frage der eloquenten Auseinandersetzung mit den sich überlagernden Bedenken der Entsorgung und Finalisierung.

Wenn wir entsorgen wollen wir:

  1. Entsorgen Sie alle Einweg-Mitgliedsobjekte.
  2. Entsorgen Sie das Basisobjekt.
  3. Nicht verwaltete Ressourcen freigeben.

Wenn Abschluss möchten wir:

  1. Veröffentlichung nicht verwalteten Ressourcen.

Hinzu kommen folgende Bedenken:

  1. Entsorgung sicher sein sollte mehrmals aufrufen. Es sollte kein Fehler sein, um x.Dispose();x.Dispose();
  2. aufzurufen Finalisierung fügt eine Belastung zur Garbage Collection hinzu. Wenn wir es vermeiden, wenn wir können, insbesondere wenn wir bereits nicht verwaltete Ressourcen freigegeben haben, möchten wir die Finalisierung unterdrücken, da sie nicht mehr benötigt wird.
  3. Der Zugriff auf finalisierte Objekte ist schwierig. Wenn ein Objekt finalisiert wird, können alle finalisierbaren Mitglieder (die sich auch mit den gleichen Anliegen wie unsere Klasse befassen) bereits fertig gestellt sein und werden sich definitiv in der Finalisierungswarteschlange befinden. Da es sich bei diesen Objekten wahrscheinlich auch um verwaltete Einwegobjekte handelt, und wenn diese entsorgt werden, werden ihre nicht verwalteten Ressourcen freigegeben, und in einem solchen Fall wollen wir sie nicht entsorgen.

Der Code, den Sie geben wird (wenn Sie in der finaliser hinzufügen, die Dispose(false) diese Bedenken Anrufe verwalten. Im Fall von Dispose() aufgerufen wird es werden beide verwaltete und nicht verwaltete Mitglieder aufzuräumen und Finalisierung unterdrücken, während auch gegen mehrere Bewachung (In dieser Hinsicht ist es jedoch nicht thread-sicher.) Im Fall des Aufrufs des Finalisierers bereinigt er nicht verwaltete Elemente.

Dieses Muster wird jedoch nur vom Anti-Pattern der Kombination von verwaltet und benötigt Unmanaged Bedenken in der gleichen Klasse Ein viel besserer Ansatz besteht darin, alle nicht verwalteten Ressourcen über eine Klasse zu behandeln, die nur mit dieser Ressource befasst ist, egal ob SafeHandle oder eine separate Klasse von dir selbst. Dann werden Sie eine von zwei Mustern haben, von denen die letztere selten sein wird:

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.*/ 
    } 
} 

Dies keinen finaliser hat und keiner braucht.

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(); 
    } 
} 

Diese Version, die viel seltener sein wird (auch nicht in den meisten Projekten geschieht) die Entsorgung Deal nur mit dem einzigen nicht verwaltete Ressource mit befasst, für welche die Klasse ein Wrapper ist, und die finaliser tut das gleiche wenn die Entsorgung nicht stattgefunden hat.

Da SafeHandle das zweite Muster für Sie verwendet, sollten Sie es nicht wirklich benötigen. In jedem Fall wird das erste Beispiel, das ich gebe, die große Mehrheit der Fälle behandeln, in denen Sie IDisposable implementieren müssen. Das in Ihrem Beispiel angegebene Muster sollte nur für die Abwärtskompatibilität verwendet werden, z. B. wenn Sie von einer Klasse ableiten, die es verwendet.

0

Wenn Ihre Klasse direkt nicht verwaltete Ressourcen enthält oder von einer abgeleiteten Klasse geerbt wird, bietet das Dispositionsmuster von Microsoft eine gute Möglichkeit, Finalizer und Disponent miteinander zu verknüpfen. Wenn es keine realistische Möglichkeit gibt, dass entweder Ihre Klasse oder ihre Nachkommen direkt nicht verwaltete Ressourcen enthalten, sollten Sie den Vorlagencode löschen und Dispose einfach direkt implementieren. Angesichts der Tatsache, dass Microsoft dringend empfohlen hat, nicht verwaltete Ressourcen in Klassen zu verpacken, deren einziger Zweck es ist, sie zu halten (*) (und Klassen wie SafeHandle für genau diesen Zweck hat), ist der Vorlagencode wirklich nicht mehr nötig.

(*) Garbage Collection in .net ist ein mehrstufiger Prozess; Zuerst ermittelt das System, welche Objekte nirgendwo referenziert werden. dann erstellt es eine Liste von Finalize'able Objekten, die nirgendwo referenziert werden. Die Liste und alle darauf enthaltenen Objekte werden wieder "live" deklariert, was bedeutet, dass alle Objekte, auf die sie verweisen, ebenfalls live sind. An diesem Punkt wird das System eine tatsächliche Speicherbereinigung durchführen; Dann werden alle Finalizer auf der Liste ausgeführt. Wenn ein Objekt z. eine direkte Handle auf eine Font-Ressource (nicht verwaltet) sowie Verweise auf zehn andere Objekte, die wiederum direkte oder indirekte Verweise auf einhundert mehr Objekte enthalten, dann wird das Objekt aufgrund der nicht verwalteten Ressource einen Finalizer benötigen. Wenn das Objekt zur Abholung fällig ist, können weder es noch die über 100 Objekte, für die es direkte oder indirekte Referenzen besitzt, bis zum Abschluss des Finalizers gesammelt werden.

Wenn statt einer direkten Handle zu der Schriftart-Ressource das Objekt einen Verweis auf ein Objekt hielt, das die Schriftartenressource (und nichts anderes) enthält, würde das letztere Objekt einen Finalizer benötigen, aber der ehemalige würde nicht (da es keinen direkten Verweis auf die nicht verwaltete Ressource enthält, nur ein Objekt (dasjenige, das den Finalizer enthielt), anstatt 100+, würde die erste Speicherbereinigung überleben müssen

5

Niemand kam zu der die letzten beiden Fragen (BTW: frage nur eine pro Thread) Die Verwendung einer Sperre in Dispose() ist ziemlich tödlich für den Finalizer-Thread Es gibt keine Obergrenze für die Dauer der Sperre, Ihr Programm stürzt nach zwei Sekunden ab Die CLR bemerkt, dass der Finalizer-Thread hängen geblieben ist. Außerdem ist es nur ein Fehler. Sie sollten Dispose() nicht aufrufen, wenn ein anderer Thread möglicherweise noch einen Verweis auf das Objekt hat.

Ja, die Implementierung eines Finalizers ohne Implementierung von IDisposable ist nicht unbekannt. Jeder COM-Objekt-Wrapper (RCW) macht das. So auch die Thread-Klasse. Dies wurde gemacht, weil es einfach nicht sinnvoll ist, Dispose() aufzurufen. Im Fall eines COM-Wrappers, weil es einfach nicht möglich ist, alle Referenzzählungen zu verfolgen. Im Falle von Thread, weil der Join() der Thread, so dass Sie Dispose() aufrufen können, den Zweck eines Threads vereitelt.

Achten Sie auf Jon Hannas Beitrag. Die Implementierung eines eigenen Finalizers ist in 99,99% der Fälle falsch. Sie haben die SafeHandle-Klassen, um nicht verwaltete Ressourcen zu verpacken. Du brauchst etwas ziemlich Unklares, um nicht von ihnen umhüllt zu werden.

+0

Verwaltete Threads werden während der Garbage-Collection angehalten, aber ich denke nicht, dass sie während der Finalisierung gesperrt sind. Die Art und Weise der Finalisierung, Objekte, für die die Finalisierung aktiviert ist, und alle Objekte, auf die sie direkt oder indirekt verweisen, werden nicht als Garbage-Collection behandelt. Wenn sie jedoch für die Garbage-Collection geeignet sind, werden sie nach der aktuellen Garbage-Collection abgeschlossen Pass ist abgeschlossen. Sobald die Finalizer ausgeführt wurden, können die Objekte nicht mehr für die Finalisierung ausgewählt werden, wenn sie sich nicht erneut für die Finalisierung registrieren. – supercat

+0

@super - du hast Recht, ich bin mir nicht sicher, warum ich das geschrieben habe. Schaden rückgängig gemacht, danke. –

+0

@Hans: Ich denke, die Klasse Thread verwendet Finalizer, um die Zuordnung von verwalteten Thread-IDs zu bereinigen. Jedes separat erstellte Thread-Objekt muss eine eindeutige ID haben. Da die Thread-IDs nur 32 Bit betragen, muss das Framework in der Lage sein, sie wiederzuverwenden (andernfalls würde jedes Programm, das während seiner Lebensdauer zwei Milliarden Thread-Objekte erstellt hat, abstürzen). Da schlechte Dinge passieren können, wenn zwei Thread-Objekte dieselbe ManagedThreadID haben, werden Finalizer verwendet, um sicherzustellen, dass IDs nur zur Wiederverwendung verfügbar gemacht werden, wenn keine Referenzen mehr auf die Thread-Objekte vorhanden sind, die sie enthalten. – supercat