Der Sinn von Dispose es um nicht verwaltete Ressourcen freizugeben. Dies muss zu einem bestimmten Zeitpunkt geschehen, da die Ressourcen sonst nie aufgeräumt werden. Der Garbage Collector weiß nicht wie anrufen DeleteHandle()
auf eine Variable des Typs IntPtr
weiß es nicht. ob oder nicht, muss es die DeleteHandle()
.
Nota : Was ist ein unverwaltete Ressource ? Wenn Sie es im Microsoft .NET Framework gefunden haben: Es ist verwaltet. Wenn Sie selbst auf MSDN nachgeschaut haben, ist es nicht verwaltet. Alles, was Sie mit Hilfe von P/Invoke-Aufrufen außerhalb der netten, bequemen Welt von allem, was Ihnen im .NET Framework zur Verfügung steht, erreicht haben, ist nicht verwaltet - und Sie sind jetzt dafür verantwortlich, es zu bereinigen.
Das Objekt, das Sie erstellt haben, muss die einige Methode, die die Außenwelt aufrufen kann, um nicht verwaltete Ressourcen zu bereinigen. Die Methode kann beliebig benannt werden:
public void Cleanup()
ou
public void Shutdown()
Stattdessen gibt es aber eine standardisierte Bezeichnung für diese Methode:
public void Dispose()
Es wurde sogar eine Schnittstelle geschaffen, IDisposable
die nur diese eine Methode hat:
public interface IDisposable
{
void Dispose()
}
Sie machen also Ihr Objekt mit der IDisposable
Schnittstelle, und auf diese Weise versprechen Sie, dass Sie diese eine Methode geschrieben haben, um Ihre nicht verwalteten Ressourcen zu bereinigen:
public void Dispose()
{
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
Und schon sind Sie fertig. Aber Sie können es besser.
Was ist, wenn Ihr Objekt 250 MB zugewiesen hat? System.Drawing.Bitmap (d. h. die verwaltete .NET-Bitmap-Klasse) als eine Art Bildpuffer? Sicher, dies ist ein verwaltetes .NET-Objekt, und der Garbage Collector wird es freigeben. Aber wollen Sie wirklich 250 MB Speicher einfach so stehen lassen - und darauf warten, dass der Garbage Collector eventuell kommen und es befreien? Was, wenn es eine Datenbankverbindung öffnen ? Sicherlich wollen wir nicht, dass die Verbindung offen bleibt und darauf wartet, dass der GC das Objekt abschließt.
Wenn der Benutzer die Dispose()
(d.h. sie planen nicht mehr, das Objekt zu verwenden), warum dann nicht diese verschwenderischen Bitmaps und Datenbankverbindungen loswerden?
Das werden wir jetzt tun:
- nicht verwaltete Ressourcen loszuwerden (weil wir es müssen), und
- Abschaffung der verwalteten Ressourcen (weil wir hilfreich sein wollen)
Aktualisieren wir also unsere Dispose()
Methode, um diese verwalteten Objekte loszuwerden:
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;
}
}
Und alles ist gut, außer Sie können es besser !
Was, wenn die Person vergessen anrufen Dispose()
auf Ihr Objekt? Dann würden sie etwas auslaufen unverwaltet Ressourcen!
Nota: Sie werden nicht auslaufen verwaltet Ressourcen, denn schließlich wird der Garbage Collector in einem Hintergrund-Thread ausgeführt und gibt den Speicher für alle nicht verwendeten Objekte frei. Dazu gehören Ihr Objekt und alle verwalteten Objekte, die Sie verwenden (z. B. die Bitmap
und die DbConnection
).
Wenn die Person vergessen hat, anzurufen Dispose()
können wir immer noch Rette ihren Speck! Wir haben immer noch eine Möglichkeit, es zu nennen für sie: wenn der Garbage Collector endlich dazu kommt, unser Objekt freizugeben (d.h. zu finalisieren).
Nota: Der Garbage Collector wird schließlich alle verwalteten Objekte freigeben. Wenn er dies tut, ruft er die Finalize
Methode auf das Objekt. Der GC weiß nicht, oder interessiert sich nicht für Ihr Entsorgen Sie Methode. Das war nur ein Name, den wir für eine Methode, die wir aufrufen, wenn wir nicht verwaltetes Zeug loswerden wollen.
Die Zerstörung unseres Objekts durch den Garbage Collector ist die perfekt Zeit, diese lästigen, nicht verwalteten Ressourcen freizugeben. Wir tun dies durch Überschreiben der Finalize()
méthode.
Nota: In C# überschreiben Sie nicht explizit die Finalize()
Methode. Sie schreiben eine Methode, die sieht aus wie a C++ Destruktor , und der Compiler geht davon aus, dass dies Ihre Implementierung der Finalize()
Methode:
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
Aber es gibt einen Fehler in diesem Code. Sehen Sie, der Garbage Collector läuft auf einer Hintergrund-Thread Sie kennen die Reihenfolge, in der zwei Objekte zerstört werden, nicht. Es ist durchaus möglich, dass in Ihrem Dispose()
Code, der verwaltet Der Gegenstand, den Sie loswerden wollten (weil Sie hilfreich sein wollten), ist nicht mehr da:
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;
}
}
Was Sie also brauchen, ist eine Möglichkeit für Finalize()
zu erzählen Dispose()
dass sie keine verwalteten Ressourcen (weil sie vielleicht nicht da sein mehr), während gleichzeitig nicht verwaltete Ressourcen freigegeben werden.
Das Standardmuster hierfür ist, dass Finalize()
y Dispose()
rufen beide eine dritte (!)-Methode, bei der Sie einen booleschen Wert übergeben, der angibt, ob Sie die Methode von Dispose()
(im Gegensatz zu Finalize()
), d.h. es ist sicher, verwaltete Ressourcen freizugeben.
Ce site intern Methode könnte einen willkürlichen Namen wie "CoreDispose" oder "MyInternalDispose" erhalten, aber es ist üblich, es als Dispose(Boolean)
:
protected void Dispose(Boolean disposing)
Ein hilfreicherer Parametername könnte jedoch lauten:
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;
}
}
}
Und Sie ändern Ihre Implementierung der IDisposable.Dispose()
Methode zu:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
und Ihren Finalizer an:
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
Nota : Wenn Ihr Objekt von einem Objekt abstammt, das die Funktion Dispose
dann vergessen Sie nicht, ihre Basis Dispose-Methode, wenn Sie Dispose überschreiben:
public override void Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
Und alles ist gut, außer Sie können es besser !
Ruft der Benutzer Dispose()
auf Ihr Objekt, dann ist alles aufgeräumt. Später, wenn der Garbage Collector kommt und Finalize aufruft, ruft er dann Dispose
encore.
Dies ist nicht nur verschwenderisch, sondern wenn Ihr Objekt Junk-Referenzen auf Objekte enthält, die Sie bereits aus der zuletzt Aufruf an Dispose()
werden Sie versuchen, sie wieder zu entsorgen!
Sie werden feststellen, dass ich in meinem Code darauf geachtet habe, Verweise auf Objekte, die ich entsorgt habe, zu entfernen, so dass ich nicht versuche, die Dispose
auf ein Junk-Objekt verweisen. Das hat aber nicht verhindert, dass sich ein subtiler Fehler eingeschlichen hat.
Wenn der Benutzer aufruft Dispose()
: der Griff CursorFileBitmapIconServiceHandle zerstört wird. Später, wenn der Garbage Collector läuft, wird er versuchen, das gleiche Handle erneut zu zerstören.
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy
...
}
Sie können dies beheben, indem Sie dem Garbage Collector mitteilen, dass er sich nicht um die Finalisierung des Objekts kümmern muss - seine Ressourcen wurden bereits aufgeräumt, und es ist keine weitere Arbeit erforderlich. Sie tun dies durch den Aufruf GC.SuppressFinalize()
im Dispose()
Methode:
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
}
Nachdem der Benutzer nun die Dispose()
haben wir:
- frei gewordene, nicht verwaltete Ressourcen
- frei gewordene verwaltete Ressourcen
Es macht keinen Sinn, dass der GC den Finalizer ausführt - alles ist bereits erledigt.
Könnte ich nicht Finalize verwenden, um nicht verwaltete Ressourcen zu bereinigen?
Die Dokumentation für Object.Finalize
sagt:
Die Finalize-Methode wird verwendet, um Aufräumarbeiten an nicht verwalteten Ressourcen durchzuführen, die vom aktuellen Objekt gehalten werden, bevor das Objekt zerstört wird.
Aber die MSDN-Dokumentation sagt auch, dass für IDisposable.Dispose
:
Führt anwendungsdefinierte Aufgaben im Zusammenhang mit dem Freigeben, Freigeben oder Zurücksetzen von nicht verwalteten Ressourcen durch.
Welches ist es also? Welches ist der richtige Ort für mich, um nicht verwaltete Ressourcen zu bereinigen? Die Antwort ist:
Sie haben die Wahl! Aber wähle Dispose
.
Sie können Ihre nicht verwaltete Bereinigung natürlich im Finalizer unterbringen:
~MyObject()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//A C# destructor automatically calls the destructor of its base class.
}
Das Problem dabei ist, dass Sie keine Ahnung haben, wann der Garbage Collector Ihr Objekt abschließen wird. Ihre nicht verwalteten, nicht benötigten und nicht genutzten nativen Ressourcen bleiben bestehen, bis der Garbage Collector eventuell läuft. Dann wird Ihre Finalizer-Methode aufgerufen, um nicht verwaltete Ressourcen zu bereinigen. Die Dokumentation von Object.Finalize weist auf Folgendes hin:
Der genaue Zeitpunkt, zu dem der Finalizer ausgeführt wird, ist nicht festgelegt. Um eine deterministische Freigabe von Ressourcen für Instanzen Ihrer Klasse zu gewährleisten, implementieren Sie eine Schließen Sie Methode oder bieten eine IDisposable.Dispose
Umsetzung.
Dies ist der Vorzug der Verwendung von Dispose
um nicht verwaltete Ressourcen zu bereinigen; Sie können wissen und kontrollieren, wann nicht verwaltete Ressourcen bereinigt werden. Ihre Zerstörung ist "deterministisch" .
Um Ihre ursprüngliche Frage zu beantworten: Warum wird der Speicher nicht jetzt freigegeben, sondern erst dann, wenn der GC dies beschließt? Ich habe eine Gesichtserkennungssoftware, die braucht um 530 MB interne Bilder loszuwerden jetzt da sie nicht mehr benötigt werden. Wenn wir das nicht tun, kommt die Maschine zum Stillstand.
Bonus Lesung
Für alle, denen der Stil dieser Antwort gefällt (Erläuterung der warum , so dass die wie deutlich wird), empfehle ich Ihnen die Lektüre von Kapitel eins des Buches Essential COM von Don Box:
Auf 35 Seiten erklärt er die Probleme bei der Verwendung binärer Objekte und erfindet vor Ihren Augen COM. Sobald Sie die warum von COM, die restlichen 300 Seiten sind offensichtlich und beschreiben lediglich die Implementierung von Microsoft.
Ich denke, jeder Programmierer, der sich jemals mit Objekten oder COM beschäftigt hat, sollte zumindest das erste Kapitel lesen. Es ist die beste Erklärung von allem überhaupt.
Extra Bonus Lektüre
Wenn alles, was du weißt, falsch ist Archiv von Eric Lippert
Es ist daher in der Tat sehr schwierig, einen korrekten Finalizer zu schreiben, und Der beste Rat, den ich Ihnen geben kann, ist, es nicht zu versuchen. .