Frage Richtige Verwendung der IDisposable-Schnittstelle


Ich weiß es vom Lesen die MSDN-Dokumentation dass die "primäre" Verwendung der IDisposable Die Benutzeroberfläche besteht darin, nicht verwaltete Ressourcen zu bereinigen.

"Unmanaged" bedeutet für mich Dinge wie Datenbankverbindungen, Sockets, Fenstergriffe usw. Aber ich habe Code gesehen, wo die Dispose() Methode wird implementiert, um zu befreien gelang es Ressourcen, die mir überflüssig erscheinen, da der Müllsammler sich darum kümmern sollte.

Beispielsweise:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Meine Frage ist, macht dies den Müllsammler freien Speicher von verwendet MyCollection etwas schneller als es normalerweise wäre?

bearbeiten: Bisher haben einige gute Beispiele für die Verwendung von IDisposable zur Bereinigung nicht verwalteter Ressourcen wie Datenbankverbindungen und Bitmaps gepostet. Aber stell dir das vor _theList im obigen Code enthielt eine Million Strings, und Sie wollten diesen Speicher freigeben jetzt, anstatt auf den Müllsammler zu warten. Würde der obige Code das erreichen?


1378
2018-02-11 18:12


Ursprung


Antworten:


Der Punkt von Dispose ist um nicht verwaltete Ressourcen zu befreien. Es muss irgendwann gemacht werden, sonst werden sie nie wieder sauber gemacht. Der Müllsammler weiß es nicht Wie anrufen DeleteHandle() auf einer Variablen des Typs IntPtrEs weiß nicht ob oder nicht muss es anrufen DeleteHandle().

Hinweis: Was ist ein nicht verwaltete Ressource? Wenn Sie es in Microsoft .NET Framework gefunden haben: Es ist verwaltet. Wenn Sie MSDN selbst durchsuchen, ist es nicht verwaltet. Alles, was Sie mit P / Invoke aufgerufen haben, um außerhalb der netten, bequemen Welt von allem zu kommen, das Ihnen in .NET Framework zur Verfügung steht, ist nicht verwaltet - und Sie sind jetzt dafür verantwortlich, es zu bereinigen.

Das Objekt, das Sie erstellt haben, muss verfügbar gemacht werden etwas Methode, die die Außenwelt aufrufen kann, um nicht verwaltete Ressourcen zu bereinigen. Die Methode kann wie immer benannt werden:

public void Cleanup()

public void Shutdown()

Stattdessen gibt es einen standardisierten Namen für diese Methode:

public void Dispose()

Es wurde sogar eine Schnittstelle erstellt, IDisposable, das hat genau diese eine Methode:

public interface IDisposable
{
   void Dispose()
}

So lässt du dein Objekt dem aussetzen 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 du bist fertig. Außer du kannst es besser machen.


Was ist, wenn Ihr Objekt 250 MB zugewiesen hat? System.Drawing.Bitmap (d. h. die .NET verwaltete Bitmap-Klasse) als eine Art Bildpuffer? Sicher, dies ist ein verwaltetes .NET-Objekt, und der Garbage Collector wird es befreien. Aber willst du wirklich 250MB Speicher lassen, die nur dort sitzen und auf den Müllsammler warten? schließlich Komm mit und befreie es? Was ist, wenn es einen gibt? Datenbankverbindung öffnen? Sicherlich wollen wir nicht, dass diese Verbindung offen steht und darauf warten, dass der GC das Objekt finalisiert.

Wenn der Benutzer angerufen hat Dispose() (was bedeutet, dass sie das Objekt nicht mehr verwenden wollen) Warum nicht diese verschwenderischen Bitmaps und Datenbankverbindungen loswerden?

So, jetzt werden wir:

  • loswerden von nicht verwalteten Ressourcen (weil wir müssen), und
  • Verwaltete Ressourcen loswerden (weil wir hilfreich sein wollen)

Also lasst uns unsere aktualisieren 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 du kannst es besser machen!


Was ist, wenn die Person? vergessen anrufen Dispose() auf dein Objekt? Dann würden sie etwas auslecken nicht verwaltet Ressourcen!

Hinweis: Sie werden nicht lecken gelang es Ressourcen, weil schließlich der Garbage Collector ausgeführt wird, auf einem Hintergrundthread, und den Speicher freigeben, der mit nicht verwendeten Objekten verbunden ist. Dies umfasst Ihr Objekt und alle verwalteten Objekte, die Sie verwenden (z. B. das Bitmap und das DbConnection).

Wenn die Person vergessen hat anzurufen Dispose(), wir können immer noch rette ihren Speck! Wir haben immer noch eine Möglichkeit, es zu nennen zum sie: wenn der Müllsammler endlich herumkommt, um unser Objekt zu befreien (d. h. finalisieren).

Hinweis: Der Garbage Collector wird schließlich alle verwalteten Objekte freigeben.   Wenn es das tut, ruft es die Finalize   Methode für das Objekt. Der GC weiß nicht, oder   Wert darauf legen Ihre  Entsorgen Methode.   Das war nur ein Name, für den wir uns entschieden haben   eine Methode, die wir aufrufen, wenn wir wollen   los von unmanaged Zeug.

Die Zerstörung unseres Objekts durch den Müllsammler ist der perfekt Zeit, diese lästigen unmanaged Ressourcen zu befreien. Wir tun dies, indem wir die Finalize() Methode.

Hinweis: In C # überschreiben Sie nicht ausdrücklich die Finalize() Methode.   Du schreibst eine Methode, die sieht aus wie ein C ++ Destruktor, und das   Compiler nimmt das als 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. Sie sehen, der Garbage Collector läuft auf einem Hintergrundfaden; Sie kennen nicht die Reihenfolge, in der zwei Objekte zerstört werden. Es ist durchaus möglich, dass in Ihrem Dispose() Code, der gelang es Objekt das du versuchst loszuwerden (weil du hilfreich sein wolltest) 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;
   }
}

Also, was Sie brauchen, ist ein Weg für Finalize() erzählen Dispose() das sollte es nicht berühren, verwaltet Ressourcen (weil sie vielleicht nicht da sein (mehr), während noch nicht verwaltete Ressourcen freigegeben werden.

Das Standardmuster dafür ist zu haben Finalize() und Dispose() beide rufen einen an dritte(!) Methode; wo Sie ein boolesches Sprichwort übergeben, wenn Sie es von anrufen Dispose() (im Gegensatz zu Finalize()), dh es ist sicher, verwaltete Ressourcen zu befreien.

Dies intern Methode könnte Geben Sie einen beliebigen Namen wie "CoreDispose" oder "MyInternalDispose", aber es ist Tradition, es zu nennen Dispose(Boolean):

protected void Dispose(Boolean disposing)

Aber ein hilfreicher Parametername könnte sein:

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 dein Finalizer für:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Hinweis: Wenn Ihr Objekt von einem Objekt stammt, das implementiert DisposeDann vergiss nicht, sie anzurufen Base Dispose-Methode beim Überschreiben von Dispose:

public Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

Und alles ist gut, außer du kannst es besser machen!


Wenn der Benutzer anruft Dispose() auf dein Objekt, dann ist alles aufgeräumt. Später, wenn der Garbage Collector kommt und Finalize aufruft, wird er dann anrufen Dispose nochmal.

Dies ist nicht nur verschwenderisch, sondern wenn Ihr Objekt Junk-Referenzen auf Objekte hat, die Sie bereits aus dem Internet entsorgt haben letzte Aufruf Dispose(), Sie werden versuchen, sie wieder zu entsorgen!

Sie werden feststellen, dass ich in meinem Code darauf geachtet habe, Verweise auf Objekte zu entfernen, die ich entsorgt habe. Daher versuche ich nicht, einen Anruf zu tätigen Dispose auf eine Junk-Objekt-Referenz. Aber das hat einen kleinen Fehler nicht verhindert.

Wenn der Benutzer anruft Dispose(): der Griff CursorFileBitmapIconServiceHandle ist zerstört. Später, wenn der Garbage Collector ausgeführt wird, wird es versuchen, den gleichen Handle erneut zu zerstören.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

Die Art und Weise, wie Sie das Problem beheben, ist, dass der Garbage Collector nicht mit der Fertigstellung des Objekts befasst werden muss - seine Ressourcen wurden bereits bereinigt und es ist keine weitere Arbeit erforderlich. Sie tun dies, indem Sie anrufen GC.SuppressFinalize() in dem 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
}

Jetzt hat der Benutzer angerufen Dispose(), wir haben:

  • freigegebene nicht verwaltete Ressourcen
  • freigegebene verwaltete Ressourcen

Es hat keinen Sinn, dass der GC den Finalizer laufen lässt - alles ist erledigt.

Konnte ich Finalize nicht zum Bereinigen nicht verwalteter Ressourcen verwenden?

Die Dokumentation für Object.Finalize sagt:

Mit der Finalize-Methode werden Bereinigungsvorgänge für nicht verwaltete Ressourcen ausgeführt, die vom aktuellen Objekt vor der Zerstörung des Objekts gehalten werden.

Aber die MSDN-Dokumentation sagt auch, z IDisposable.Dispose:

Führt anwendungsdefinierte Aufgaben im Zusammenhang mit dem Freigeben, Freigeben oder Zurücksetzen nicht verwalteter Ressourcen aus.

Also was ist es? Welches ist der Ort für mich, um nicht verwaltete Ressourcen zu bereinigen? Die Antwort ist:

Es ist Ihre Wahl! Aber wähle Dispose.

Sie könnten Ihre nicht verwaltete Bereinigung im Finalizer platzieren:

~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 finalisieren wird. Ihre nicht verwalteten, nicht benötigten, nicht verwendeten systemeigenen Ressourcen bleiben bis zum Garbage Collector bestehen schließlich läuft. Dann wird es Ihre Finalizer-Methode aufrufen; unmanaged Ressourcen bereinigen. Die Dokumentation von Object.Finalize weist darauf hin:

Der genaue Zeitpunkt, zu dem der Finalizer ausgeführt wird, ist nicht definiert. Um eine deterministische Freigabe von Ressourcen für Instanzen Ihrer Klasse zu gewährleisten, implementieren Sie a Schließen Methode oder a IDisposable.Dispose Implementierung.

Dies ist die Tugend der Verwendung Dispose Unverwaltete Ressourcen bereinigen; Sie erfahren und kontrollieren, wenn die nicht verwaltete Ressource bereinigt wird. Ihre Zerstörung ist "deterministisch".


Um Ihre ursprüngliche Frage zu beantworten: Warum nicht jetzt Speicher freigeben und nicht, wenn der GC entscheidet, es zu tun? Ich habe eine Gesichtserkennungssoftware, die braucht um 530 MB interne Bilder loszuwerden jetzt, da sie nicht mehr benötigt werden. Wenn nicht, läuft die Maschine zum Stillstand.

Bonuslesen

Für jeden, der den Stil dieser Antwort mag (erklärt den Warumso Wie wird offensichtlich), ich schlage vor, Sie lesen Kapitel 1 von Don Box Essential COM:

Auf 35 Seiten erklärt er die Probleme der Verwendung von binären Objekten und erfindet COM vor Ihren Augen. Sobald du das realisierst Warum von COM sind die restlichen 300 Seiten offensichtlich und nur Details der 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 Lesen

Wenn alles, was du weißt, falsch ist von Eric Lippert

Es ist daher sehr schwierig, einen korrekten Finalizer zu schreiben,   und Der beste Rat, den ich Ihnen geben kann, ist nicht zu versuchen.


2286
2018-02-11 18:20



IDisposable wird häufig verwendet, um die using Anweisung und nutzen Sie die Vorteile einer einfachen Methode zur deterministischen Bereinigung verwalteter Objekte.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

54
2018-02-11 18:42



Der Zweck des Dispose-Musters besteht darin, einen Mechanismus zum Bereinigen von verwalteten und nicht verwalteten Ressourcen bereitzustellen, und wenn dies auftritt, hängt es davon ab, wie die Dispose-Methode aufgerufen wird. In Ihrem Beispiel führt die Verwendung von Dispose nicht zu einer Entsorgung, da das Löschen einer Liste keine Auswirkungen auf die entsorgte Sammlung hat. Ebenso haben die Aufrufe, die Variablen auf null zu setzen, keine Auswirkungen auf den GC.

Sie können sich das ansehen Artikel Weitere Informationen zum Implementieren des Dispose-Musters finden Sie im Folgenden:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

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

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Die Methode, die hier am wichtigsten ist, ist das Dispose (bool), das eigentlich unter zwei verschiedenen Umständen läuft:

  • disposing == true: Die Methode wurde direkt oder indirekt von einem Benutzercode aufgerufen. Verwaltete und nicht verwaltete Ressourcen können entsorgt werden.
  • disposing == false: Die Methode wurde von der Laufzeit aus dem Finalizer aufgerufen, und Sie sollten nicht auf andere Objekte verweisen. Nur nicht verwaltete Ressourcen können entsorgt werden.

Das Problem, den GC einfach die Säuberung durchführen zu lassen, besteht darin, dass Sie keine Kontrolle darüber haben, wann der GC einen Erfassungszyklus ausführt (Sie können GC.Collect () aufrufen, aber das sollten Sie wirklich nicht) um länger als nötig. Denken Sie daran, dass der Aufruf von Dispose () keinen Sammlungszyklus auslöst oder in irgendeiner Weise dazu führt, dass der GC das Objekt sammelt / freigibt; Es bietet einfach die Möglichkeit, die verwendeten Ressourcen deterministischer zu bereinigen und dem GC mitzuteilen, dass diese Bereinigung bereits durchgeführt wurde.

Der Hauptpunkt von IDisposable und dem Dispose-Muster besteht nicht darin, Speicher sofort freizugeben. Der einzige Zeitpunkt, an dem ein Dispose-Aufruf tatsächlich eine Chance hat, Speicher sofort freizugeben, ist, wenn er das Szenario disposing == false verarbeitet und nicht verwaltete Ressourcen manipuliert. Bei verwaltetem Code wird der Speicher erst wieder freigegeben, wenn der GC einen Erfassungszyklus ausführt, über den Sie wirklich keine Kontrolle haben (außer dem Aufruf von GC.Collect (), was ich bereits erwähnt habe, ist keine gute Idee).

Ihr Szenario ist nicht wirklich gültig, da Zeichenfolgen in .NET keine nicht verknüpften Ressourcen verwenden und IDisposable nicht implementieren. Es gibt keine Möglichkeit, sie zu zwingen, "bereinigt" zu werden.


36
2018-02-11 20:21



Es sollte keine weiteren Aufrufe der Methoden eines Objekts geben, nachdem Dispose aufgerufen wurde (obwohl ein Objekt weitere Aufrufe von Dispose tolerieren sollte). Daher ist das Beispiel in der Frage dumm. Wenn Dispose aufgerufen wird, kann das Objekt selbst verworfen werden. Also sollte der Benutzer einfach alle Referenzen auf das ganze Objekt verwerfen (setze sie auf null) und alle damit verbundenen Objekte werden automatisch bereinigt.

Was die allgemeine Frage zu managed / unmanaged und die Diskussion in anderen Antworten anbelangt, denke ich, dass jede Antwort auf diese Frage mit einer Definition einer nicht verwalteten Ressource beginnen muss.

Worauf es hinausläuft ist, dass es eine Funktion gibt, die Sie aufrufen können, um das System in einen Zustand zu versetzen, und es gibt eine andere Funktion, die Sie aufrufen können, um es aus diesem Zustand zurückzuholen. Nun, im typischen Beispiel könnte die erste eine Funktion sein, die ein Datei-Handle zurückgibt, und die zweite könnte ein Aufruf an sein CloseHandle.

Aber - und das ist der Schlüssel - sie könnten jedes passende Paar von Funktionen sein. Der eine baut einen Staat auf, der andere reißt ihn nieder. Wenn der Status bereits erstellt, aber noch nicht abgerissen wurde, existiert eine Instanz der Ressource. Sie müssen dafür sorgen, dass der Teardown zur richtigen Zeit stattfindet - die Ressource wird nicht von der CLR verwaltet. Der einzige automatisch verwaltete Ressourcentyp ist Speicher. Es gibt zwei Arten: den GC und den Stack. Werttypen werden vom Stapel verwaltet (oder indem Sie eine Fahrt innerhalb von Referenztypen durchführen), und Referenztypen werden vom GC verwaltet.

Diese Funktionen können Zustandsänderungen verursachen, die frei verschachtelt werden können oder die perfekt ineinander verschachtelt sein müssen. Die Statusänderungen können threadsafe sein oder nicht.

Schauen Sie sich das Beispiel in der Frage von Justice an. Änderungen am Einzug der Protokolldatei müssen perfekt verschachtelt sein, sonst geht alles schief. Außerdem sind sie wahrscheinlich nicht sicher.

Es ist möglich, mit dem Garbage Collector zu fahren, um Ihre nicht verwalteten Ressourcen zu bereinigen. Aber nur, wenn die Zustandsänderungsfunktionen threadsicher sind und zwei Zustände eine Lebensdauer haben können, die sich in irgendeiner Weise überlappen. Also darf das Beispiel von Gerechtigkeit für eine Ressource keinen Finalizer haben! Es würde niemandem helfen.

Für diese Arten von Ressourcen können Sie einfach implementieren IDisposable, ohne einen Finalizer. Der Finalizer ist absolut optional - es muss sein. Dies wird in vielen Büchern beschönigt oder gar nicht erwähnt.

Sie müssen dann die verwenden using Aussage, um eine Chance zu haben, dies sicherzustellen Dispose wird genannt. Das ist im Wesentlichen so, als würde man mit dem Stack anfahren (so wie Finalizer zum GC, using ist zum Stapel).

Der fehlende Teil besteht darin, dass Sie Dispose manuell schreiben müssen, damit es auf Ihre Felder und Ihre Basisklasse zugreifen kann. C ++ / CLI-Programmierer müssen das nicht tun. Der Compiler schreibt es in den meisten Fällen für sie.

Es gibt eine Alternative, die ich für Staaten vorziehe, die perfekt ineinander verschachtelt sind und nicht threadsicher sind (abgesehen von allem anderen, vermeidet IDisposable dir das Problem, mit jemandem zu streiten, der nicht widerstehen kann, jeder Klasse, die IDisposable implementiert, einen Finalizer hinzuzufügen) .

Anstatt eine Klasse zu schreiben, schreiben Sie eine Funktion. Die Funktion akzeptiert einen Delegaten, um zurückzurufen an:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

Und dann wäre ein einfaches Beispiel:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

Das Lambda, das übergeben wird, dient als Codeblock, also ist es so, als ob Sie Ihre eigene Kontrollstruktur erstellen, die demselben Zweck dient wie using, außer dass Sie keine Gefahr mehr haben, dass der Anrufer sie missbraucht. Es gibt keine Möglichkeit, dass sie die Ressource nicht bereinigen können.

Diese Technik ist weniger nützlich, wenn die Ressource die Art hat, die sich überlappen kann, weil Sie dann Ressource A und dann Ressource B erstellen möchten, dann Ressource A beenden und dann Ressource B beenden können. Das können Sie nicht tun wenn Sie den Benutzer gezwungen haben, so perfekt zu verschachteln. Aber dann musst du verwenden IDisposable (aber immer noch ohne einen Finalizer, es sei denn, Sie haben threadsafety implementiert, die nicht frei ist).


17
2018-02-11 19:31



Szenarien, die ich IDisposable verwende: bereinigen nicht verwaltete Ressourcen, Abmeldung für Ereignisse, Schließen von Verbindungen

Das Idiom, das ich für die Implementierung von IDisposable (nicht threadsafe):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

14
2018-02-11 18:20



Ob MyCollection wird sowieso Müll gesammelt, dann solltest du es nicht entsorgen müssen. Dadurch wird die CPU mehr als nötig umgestellt und möglicherweise sogar einige vorberechnete Analysen ungültig gemacht, die der Garbage Collector bereits durchgeführt hat.

ich benutze IDisposable um sicherzustellen, dass Threads korrekt zusammen mit nicht verwalteten Ressourcen bereitgestellt werden.

BEARBEITEN Als Antwort auf Scotts Kommentar:

Das einzige Mal, wenn die GC-Leistungsmetriken betroffen sind, ist ein Aufruf der [sic] GC.Collect () -Methode.

Vom Konzept her behält der GC eine Ansicht des Objektreferenzgraphen und alle Verweise darauf von den Stapelrahmen von Fäden bei. Dieser Heap kann recht groß sein und viele Seiten Speicher umfassen. Als eine Optimierung zwischenspeichert der GC seine Analyse von Seiten, bei denen es unwahrscheinlich ist, dass sie sich sehr oft ändern, um ein unnötiges erneutes Scannen der Seite zu vermeiden. Der GC erhält eine Benachrichtigung vom Kernel, wenn sich Daten auf einer Seite ändern. Er weiß also, dass die Seite schmutzig ist und erfordert einen erneuten Scan. Wenn die Sammlung in Gen0 ist, ist es wahrscheinlich, dass sich auch andere Dinge auf der Seite ändern, aber dies ist in Gen1 und Gen2 weniger wahrscheinlich. Anekdotenweise waren diese Hooks in Mac OS X für das Team, das den GC auf Mac portiert hatte, nicht verfügbar, um das Silverlight-Plug-in auf dieser Plattform zum Laufen zu bringen.

Ein weiterer Punkt gegen die unnötige Entsorgung von Ressourcen: Stellen Sie sich eine Situation vor, in der ein Prozess entladen wird. Stellen Sie sich auch vor, dass der Prozess seit einiger Zeit läuft. Wahrscheinlich wurden viele Speicherseiten dieses Prozesses auf die Festplatte ausgelagert. Zumindest sind sie nicht mehr in L1 oder L2-Cache. In einer solchen Situation ist es für eine Anwendung, die entladen wird, keinen Sinn, alle diese Daten und Codepages wieder in den Speicher zu tauschen, um Ressourcen freizugeben, die vom Betriebssystem sowieso freigegeben werden, wenn der Prozess beendet wird. Dies gilt für verwaltete und sogar bestimmte nicht verwaltete Ressourcen. Nur Ressourcen, die Nicht-Hintergrund-Threads am Leben erhalten, müssen entsorgt werden, andernfalls bleibt der Prozess am Leben.

Jetzt, während der normalen Ausführung, gibt es ephemere Ressourcen, die korrekt bereinigt werden müssen (wie @fezmonkey darauf hinweist) Datenbankverbindungen, Sockets, Fenstergriffe) um unmanaged Speicherlecks zu vermeiden. Dies sind die Arten von Dingen, die entsorgt werden müssen. Wenn Sie eine Klasse erstellen, die einen Thread besitzt (und bei owns bedeutet, dass sie erstellt wurde und daher dafür verantwortlich ist, dass sie stoppt, zumindest nach meinem Codierungsstil), dann muss diese Klasse höchstwahrscheinlich implementiert werden IDisposable und reiß den Faden ab Dispose.

Das .NET Framework verwendet die IDisposable Schnittstelle als ein Signal, sogar Warnung, für Entwickler, die diese Klasse Muss entsorgt werden. Ich kann mir keine Typen im Rahmen vorstellen, die implementieren IDisposable (ausgenommen explizite Schnittstellenimplementierungen), bei denen die Entsorgung optional ist.


11
2018-02-11 18:19



Ja, dieser Code ist völlig redundant und unnötig und macht den Garbage Collector nicht zu etwas, was er sonst nicht tun würde (sobald eine Instanz von MyCollection außer Reichweite gerät) .Clear() Anrufe.

Antwort auf deine Bearbeitung: Sortieren von. Wenn ich das mache:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Es ist funktionell identisch zum Zweck der Speicherverwaltung:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Wenn Sie wirklich wirklich wirklich den Speicher in diesem Augenblick freigeben müssen, rufen Sie an GC.Collect(). Es gibt jedoch keinen Grund, dies hier zu tun. Der Speicher wird freigegeben, wenn es benötigt wird.


10
2018-06-03 21:07



Wenn du möchtest Löschen Sie jetzt, benutzen nicht verwalteter Speicher.

Sehen:


7
2018-02-11 21:08



Ich werde nicht die üblichen Dinge über die Verwendung oder Freigabe von nicht verwalteten Ressourcen wiederholen, die alle abgedeckt wurden. Aber ich möchte darauf hinweisen, was ein allgemeines Missverständnis scheint.
Gegeben der folgende Code

Öffentliche Klasse LargeStuff
  Implementiert IDisposable
  Privat _Large as string ()

  'Ein seltsamer Code, der _Large bedeutet, enthält jetzt mehrere Millionen lange Zeichenfolgen.

  Public Sub Dispose () implementiert IDisposable.Dispose
    _Large = Nichts
  End Sub

Mir ist klar, dass die Einweg-Implementierung nicht den aktuellen Richtlinien entspricht, aber hoffentlich verstehen Sie alle.
Wenn jetzt Dispose aufgerufen wird, wie viel Speicher wird freigegeben?

Antwort: Keine.
Durch den Aufruf von Dispose können nicht verwaltete Ressourcen freigegeben werden. Es kann keinen verwalteten Speicher zurückgewinnen, nur der GC kann dies tun. Das ist nicht zu sagen, dass das oben genannte keine gute Idee ist, das Befolgen des obigen Musters ist in der Tat immer noch eine gute Idee. Sobald Dispose ausgeführt wurde, gibt es nichts, was den GC daran hindert, den von _Large verwendeten Speicher erneut zu beanspruchen, obwohl die Instanz von LargeStuff möglicherweise noch im Bereich ist. Die Strings in _Large können auch in gen 0 sein, aber die Instanz von LargeStuff könnte gen 2 sein, also würde Speicher wieder früher beansprucht werden.
Es macht keinen Sinn, einen Finalisierer hinzuzufügen, um die oben gezeigte Dispose-Methode aufzurufen. Das verzögert nur die Speicherwiederherstellung, damit der Finalisierer laufen kann.


6
2018-02-11 21:07



In dem von Ihnen geposteten Beispiel wird der Speicher noch nicht freigegeben. Der gesamte Speicher wird als Garbage Collected (Speicherbereinigung) erfasst, kann jedoch auch die frühere Erfassung des Speichers ermöglichen Generation. Sie müssten einige Tests durchführen, um sicher zu gehen.


Die Framework Design Guidelines sind Richtlinien und keine Regeln. Sie sagen Ihnen, wofür die Schnittstelle in erster Linie bestimmt ist, wann Sie sie verwenden, wie Sie sie verwenden und wann Sie sie nicht verwenden.

Ich habe einmal Code gelesen, der ein einfaches RollBack () war, wenn IDisposable nicht verwendet wurde. Die folgende MiniTx-Klasse würde eine Markierung auf Dispose () überprüfen und wenn die Commit Anruf nie passiert, dann würde es anrufen Rollback auf sich selbst. Es hat eine indirekte Ebene hinzugefügt, die den aufrufenden Code viel einfacher zu verstehen und zu pflegen macht. Das Ergebnis sah ungefähr so ​​aus:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

Ich habe auch gesehen, Timing / Logging-Code das gleiche tun. In diesem Fall hat die Dispose () -Methode den Timer gestoppt und protokolliert, dass der Block beendet wurde.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

Hier sind ein paar konkrete Beispiele, die keine nicht verwaltete Ressourcenbereinigung durchführen, aber IDisposable erfolgreich verwenden, um saubereren Code zu erstellen.


5
2018-02-11 20:32