2008-10-24 7 views
93

Ich habe den folgenden Code:Wird ein Speicherleck erstellt, wenn ein MemoryStream in .NET nicht geschlossen ist?

MemoryStream foo(){ 
    MemoryStream ms = new MemoryStream(); 
    // write stuff to ms 
    return ms; 
} 

void bar(){ 
    MemoryStream ms2 = foo(); 
    // do stuff with ms2 
    return; 
} 

Gibt es eine Chance, dass die Memory, dass ich irgendwie später entsorgt werden zugeteilt habe scheitern?

Ich habe eine Peer Review, die darauf besteht, dass ich das manuell schließe, und ich kann die Informationen nicht finden, um zu sagen, ob er einen gültigen Punkt hat oder nicht.

+35

Fragen Sie Ihren Prüfer genau * warum * er denkt, Sie sollten ihn schließen. Wenn er über allgemeine gute Praxis spricht, ist er wahrscheinlich klug. Wenn er davon spricht, die Erinnerung früher freizugeben, liegt er falsch. –

Antwort

48

Wenn etwas wegwerfbar ist, sollten Sie es immer entsorgen. Sie sollten eine using-Anweisung in Ihrer bar() -Methode verwenden, um sicherzustellen, dass ms2 Disposed erhält.

Es wird schließlich durch den Müllsammler aufgeräumt werden, aber es ist immer gute Praxis zu Dispose. Wenn Sie FxCop für Ihren Code ausführen, würde dies als Warnung angezeigt.

+0

Also obwohl ich einen "using" -Block haben könnte, sollte ich noch .Dispose() aufrufen? –

+13

Die verwendenden Blockaufrufe sind für Sie verfügbar. – Nick

+0

Ich muss diesem Rat nicht zustimmen.Oft ist Dispose nur ein No-Op und das Aufrufen von Code stört nur den Code. –

5

Alle Streams implementieren IDisposable. Wickeln Sie Ihren Memory-Stream in eine using-Anweisung und Sie werden gut und dandy sein. Der Verwendungsblock stellt sicher, dass Ihr Stream geschlossen und entsorgt wird.

wo immer Sie Foo anrufen können Sie tun (MemoryStream ms = foo()) und ich denke, Sie sollten noch in Ordnung sein.

+1

Ein Problem, auf das ich mit dieser Gewohnheit gestoßen bin, ist, dass Sie sicher sein müssen, dass der Stream nirgendwo anders verwendet wird. Zum Beispiel habe ich einen JpegBitmapDecoder erstellt, der auf einen MemoryStream zeigte und Frames [0] zurückgab (es würde die Daten in seinen eigenen internen Speicher kopieren), aber das Bitmap würde nur in 20% der Fälle angezeigt werden Ich entsorgte den Erinnerungsstrom. – devios1

+0

Wenn Ihr Speicherstream persistent sein muss (d. H. Ein using-Block ist nicht sinnvoll), sollten Sie Dispose aufrufen und die Variable sofort auf null setzen. Wenn Sie es entsorgen, sollte es nicht mehr verwendet werden. Sie sollten es also gleich auf Null setzen. Was Chaiguy beschreibt, klingt nach einem Problem der Ressourcenverwaltung, denn Sie sollten keinen Bezug auf etwas geben, es sei denn, das Ding, dem Sie es übergeben, übernimmt die Verantwortung für die Entsorgung und die Sache, die die Referenz ausgibt, weiß, dass sie nicht mehr dafür verantwortlich ist so tun. – Triynko

1

Wenn ein Objekt IDisposable implementiert, müssen Sie die .Dispose-Methode aufrufen, wenn Sie fertig sind.

In einigen Objekten bedeutet Dispose das Gleiche wie Close und umgekehrt, in diesem Fall ist beides gut.

Nun, für Ihre spezielle Frage, nein, Sie werden Speicher nicht verlieren.

+2

"Muss" ist ein sehr starkes Wort. Wann immer es Regeln gibt, sollte man wissen, welche Folgen es hat, sie zu brechen. Für MemoryStream gibt es sehr wenige Konsequenzen. –

-1

Ich bin kein .net-Experte, aber vielleicht ist das Problem hier Ressourcen, nämlich das Datei-Handle, und nicht der Speicher. Ich nehme an, dass der Garbage Collector schließlich den Stream freigibt und den Handle schließt, aber ich denke, es wäre immer die beste Vorgehensweise, ihn explizit zu schließen, um sicherzustellen, dass Sie den Inhalt auf die Festplatte ausspülen.

+0

Ein MemoryStream ist alles im Speicher - hier gibt es kein Dateihandle. –

3

Der Aufruf .Dispose() (oder Umbruch mit Using) ist nicht erforderlich.

Der Grund, warum Sie .Dispose() anrufen, ist freigeben Sie die Ressource so schnell wie möglich.

Denken Sie zum Beispiel an den Stack Overflow-Server, wo wir einen begrenzten Speicher und Tausende von Anfragen haben. Wir wollen nicht auf die geplante Speicherbereinigung warten, sondern diesen Speicher freigeben So bald wie möglich für neue eingehende Anfragen.

+21

Das Aufrufen von Dispose auf einem MemoryStream wird jedoch keinen Speicher freigeben. In der Tat, Sie können immer noch auf die Daten in einem MemoryStream nach dem Aufruf von Dispose - versuchen Sie es :) –

+11

-1 Während es für einen MemoryStream gilt, als allgemeiner Hinweis ist dies einfach falsch. Dispose ist die Freigabe von * nicht verwalteten * Ressourcen wie Dateihandles oder Datenbankverbindungen. Speicher fällt nicht in diese Kategorie. Sie sollten fast immer auf geplante Speicherbereinigung warten, um Speicher freizugeben. – Joe

+0

Was ist der Vorteil der Annahme eines Codierungsstils für das Zuweisen und Ablegen von 'FileStream'-Objekten und eines anderen für 'MemoryStream'-Objekte? –

2

Sie werden keinen Speicher verlieren, aber Ihr Code-Prüfer zeigt an, dass Sie Ihren Stream schließen sollten. Es ist höflich, das zu tun.

Die einzige Situation, in der Sie Speicher verlieren könnten, ist, wenn Sie versehentlich einen Verweis auf den Stream hinterlassen und ihn nie schließen. Sie sind immer noch nicht wirklich undicht Speicher, aber Sie sind unnötigerweise Verlängerung der Zeit, die Sie behaupten, es zu verwenden.

+1

> Sie verlieren immer noch nicht wirklich Speicher, aber Sie verlängern unnötigerweise die Zeit, die Sie beanspruchen, es zu benutzen. Sind Sie sicher? Dispose gibt keinen Speicher frei und der Aufruf dieser Funktion kann die Zeit verlängern, die nicht gesammelt werden kann. –

+2

Ja, Jonathan hat einen Punkt. Wenn Sie spät in der Funktion einen Aufruf an Dispose senden, könnte der Compiler tatsächlich denken, dass Sie sehr spät in der Funktion auf die Stream-Instanz zugreifen müssen (um sie zu schließen). Dies könnte schlimmer sein, als überhaupt dispose nicht zu verwenden (wodurch eine späte Funktion in Bezug auf die Stromvariable vermieden wird), da ein Compiler ansonsten einen optimalen Freigabepunkt (aka "Punkt der letzten möglichen Verwendung") früher in der Funktion berechnen könnte . – Triynko

139

Sie werden nichts leaken - zumindest in der aktuellen Implementierung.

Mit Calling Dispose wird der von MemoryStream verwendete Speicher nicht schneller bereinigt. Es wird stoppen Sie Ihren Stream aus für Lesen/Schreiben Anrufe nach dem Anruf, die möglicherweise nützlich für Sie sein kann.

Wenn Sie absolut sicher sind, dass Sie nie von einem MemoryStream zu einer anderen Art von Stream verschieben möchten, wird es nicht schaden, nicht Dispose zu nennen. Allerdings ist es in der Regel eine gute Übung, zum Teil, weil, wenn Sie jemals ändern, um einen anderen Stream zu verwenden, Sie nicht von einem schwer zu finden Bug gebissen werden sollen, weil Sie den einfachen Ausweg früh gewählt haben. (Auf der anderen Seite gibt es das YAGNI Argument ...)

Der andere Grund, es trotzdem zu tun, ist, dass eine neue Implementierung einführen Ressourcen, die auf Dispose freigegeben werden würde.

+0

In diesem Fall gibt die Funktion einen MemoryStream zurück, weil sie "Daten bereitstellt, die abhängig von Aufrufparametern unterschiedlich interpretiert werden können". Es könnte also ein Byte-Array gewesen sein, aber aus anderen Gründen einfacher als ein MemoryStream. Es wird also definitiv keine weitere Stream-Klasse geben. – Coderer

+0

In diesem Fall würde ich immer noch * versuchen *, es einfach nach dem allgemeinen Prinzip zu entsorgen - gute Gewohnheiten usw. aufzubauen - aber ich würde mir keine Sorgen machen, wenn es schwierig wird. –

+0

Wenn Sie sich wirklich sorgen, dass Ressourcen so schnell wie möglich freigegeben werden, löschen Sie die Referenz unmittelbar nach dem "using" -Block, sodass nicht verwaltete Ressourcen (falls vorhanden) bereinigt werden und das Objekt für die Garbage Collection geeignet wird. Wenn die Methode sofort zurückkehrt, wird es wahrscheinlich keinen großen Unterschied machen, aber wenn Sie andere Dinge in der Methode machen, wie mehr Speicher anfordern, dann kann es sicherlich einen Unterschied machen. – Triynko

2

Ich würde empfehlen, den Memorystream in bar() in einer using Aussage vor allem für die Konsistenz Verpackung:

  • auf .Dispose() Gerade jetzt Memory nicht freien Speicher, aber es ist möglich, dass irgendwann in der es Zukunft könnte, oder Sie (oder jemand anderes in Ihrer Firma) könnte es durch Ihren eigenen benutzerdefinierten MemoryStream ersetzen, usw.
  • Es hilft, ein Muster in Ihrem Projekt einzurichten, um sicherzustellen, alle Streams entsorgt werden - die Linie ist fester gezeichnet mit dem Spruch "alle Ströme müssen entsorgt werden "Statt" müssen einige Streams entsorgt werden, aber bestimmte müssen nicht "...
  • Wenn Sie den Code ändern, um andere Arten von Streams zurückgeben zu können, müssen Sie ihn ändern, um ihn trotzdem zu entfernen .

Eine andere Sache, die ich in der Regel in Fällen wie foo() tun bei der Erstellung und der Rückkehr ein IDisposable, um sicherzustellen, dass jeder Fehler zwischen dem Objekt der Konstruktion und den return durch eine Ausnahme gefangen wird, verfügt das Objekt und rethrows die Ausnahme:

MemoryStream x = new MemoryStream(); 
try 
{ 
    // ... other code goes here ... 
    return x; 
} 
catch 
{ 
    // "other code" failed, dispose the stream before throwing out the Exception 
    x.Dispose(); 
    throw; 
} 
-2

Die Entsorgung nicht verwalteter Ressourcen ist in den gesammelten Garbage-Sprachen nicht deterministisch. Selbst wenn Sie Dispose explizit aufrufen, haben Sie absolut keine Kontrolle darüber, wann der Sicherungsspeicher tatsächlich freigegeben wird. Dispose wird implizit aufgerufen, wenn ein Objekt den Gültigkeitsbereich verlässt, sei es durch Beenden einer using-Anweisung oder durch Aufrufen des Callstacks aus einer untergeordneten Methode. Dies alles wird gesagt, manchmal kann das Objekt tatsächlich ein Wrapper für eine verwaltete Ressource (z. B. Datei) sein. Aus diesem Grund empfiehlt es sich, in finally-Anweisungen explizit zu schließen oder die using-Anweisung zu verwenden. Prost

+1

Nicht genau richtig. Dispose wird beim Beenden einer using-Anweisung aufgerufen. Dispose wird nicht aufgerufen, wenn ein Objekt den Gültigkeitsbereich verlässt. –

8

Dies ist bereits beantwortet, aber ich werde nur hinzufügen, dass die althergebrachten Prinzip der Information Hiding bedeutet, dass Sie zu einem späteren Zeitpunkt kann Refactoring wollen:

MemoryStream foo() 
{  
    MemoryStream ms = new MemoryStream();  
    // write stuff to ms  
    return ms; 
} 

zu:

Dies betont, dass Anrufer sich nicht darum kümmern sollten, welche Art von Stream zurückgegeben wird, und ermöglicht es, die interne Implementierung zu ändern (z. B. wenn man sich über Komponententests lustig macht).

Sie werden dann in Schwierigkeiten sein müssen, wenn Sie nicht entsorgen in Ihrer Bar-Implementierung verwendet haben:

void bar() 
{  
    using (Stream s = foo()) 
    { 
     // do stuff with s 
     return; 
    } 
} 
24

Ja, es ist ein Leck, je nachdem, wie Sie LEAK definieren und wie viel Sie SPÄTER bedeutet ...

Wenn Sie meinen, "der Speicher bleibt zugewiesen, nicht verfügbar für den Einsatz, auch wenn Sie fertig sind" und von letzterem meinen Sie jederzeit nach dem Aufruf dispose, dann dann kann es ein Leck sein , obwohl es nicht permanent ist (dh für das Leben Ihrer App Laufzeit).

Um den verwalteten Speicher freizugeben, der von MemoryStream verwendet wird, müssen Sie nicht referenzieren, indem Sie Ihre Referenz aufheben, damit er sofort für die Garbage Collection geeignet ist. Wenn Sie dies nicht tun, erstellen Sie ein temporäres Leck von dem Zeitpunkt an, an dem Sie es nicht mehr verwenden, bis Ihre Referenz den Gültigkeitsbereich verlässt, da der Speicher in der Zwischenzeit nicht für die Zuweisung verfügbar ist.

Der Vorteil der using-Anweisung (gegenüber dem einfachen Aufruf von dispose) besteht darin, dass Sie Ihre Referenz in der using-Anweisung deklarieren können. Wenn die using-Anweisung beendet ist, wird nicht nur dispose aufgerufen, sondern Ihre Referenz verlässt den Gültigkeitsbereich, wodurch die Referenz aufgehoben wird und Ihr Objekt sofort für die Garbage Collection geeignet ist, ohne dass Sie daran denken müssen, den Code "reference = null" zu schreiben.

Während das Versäumnis, etwas sofort zu berichtigen, kein klassisches "permanentes" Speicherleck ist, hat es definitiv den gleichen Effekt. Wenn Sie zum Beispiel Ihre Referenz auf den MemoryStream beibehalten (auch nach dem Aufruf von dispose), und ein wenig weiter unten in Ihrer Methode versuchen, mehr Speicher zuzuweisen ... ist der von Ihrem noch referenzierten Speicherstream verwendete Speicher nicht verfügbar zu dir, bis du die Referenz annullierst oder sie den Geltungsbereich verlässt, obwohl du dispose angerufen hast und sie damit gemacht hast.

+3

Ich liebe diese Antwort. Manchmal vergessen die Menschen die doppelte Pflicht zu benutzen: eifrige Ressourcen-Reklamation * und eifrige Dereferenzierung. – Kit

+1

Obwohl ich gehört habe, dass der C# -Compiler im Gegensatz zu Java die "letzte mögliche Verwendung" entdeckt, kann die Variable nach ihrer letzten Verwendung für eine Speicherbereinigung geeignet werden, wenn die Variable nach dem letzten Verweis den Gültigkeitsbereich verlassen soll. .. bevor es tatsächlich außer Reichweite gerät. Siehe http://stackoverflow.com/questions/680550/explicit-nuelling – Triynko

+1

Der Garbage Collector und der Jitter funktionieren nicht so. Scope ist eine Sprachkonstruktion und nicht etwas, dem die Laufzeit unterliegt. Tatsächlich verlängern Sie wahrscheinlich die Zeit, in der sich die Referenz im Speicher befindet, indem Sie einen Aufruf von .Dispose() hinzufügen, wenn der Block endet. Siehe http://ericlippert.com/2015/05/18/when-everything-you-know-is-wrong-part-one/ –

-4

MemorySteram ist nichts anderes als Array von Byte, das verwaltetes Objekt ist. Vergessen zu entsorgen oder zu schließen hat keine Nebenwirkung außer Überkopf der Finalisierung.
Überprüfen Sie einfach die Constructor- oder Flush-Methode von MemoryStream im Reflektor, und es ist klar, warum Sie sich nicht darum kümmern müssen, sie zu schließen oder zu entsorgen, nur um gute Praxis zu verfolgen.

+2

-1: Wenn du eine 4+ Jahre alte Frage mit posten willst eine akzeptierte Antwort, bitte versuche etwas Nützliches daraus zu machen. –

Verwandte Themen