Viene creata una perdita di memoria se un MemoryStream in .NET non è chiuso?


112

Ho il codice seguente:

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

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

C'è qualche possibilità che il MemoryStream che ho allocato in qualche modo non possa essere smaltito in seguito?

Ho una revisione tra pari che insiste sul fatto che lo chiuda manualmente e non riesco a trovare le informazioni per dire se ha un punto valido o meno.


41
Chiedi al tuo revisore esattamente perché pensa che dovresti chiuderlo. Se parla di buone pratiche generali, probabilmente è intelligente. Se parla di liberare la memoria prima, si sbaglia.
Jon Skeet

Risposte:


60

Se qualcosa è usa e getta, dovresti sempre smaltirlo. Dovresti usare una usingdichiarazione nel tuo bar()metodo per assicurarti che ms2venga eliminato.

Alla fine verrà ripulito dal Garbage Collector, ma è sempre buona norma chiamare Dispose. Se esegui FxCop sul tuo codice, lo contrassegnerà come un avviso.


16
Le chiamate di blocco using dispongono per te.
Nick

20
@ Grauenwolf: la tua affermazione rompe l'incapsulamento. Come consumatore, non dovresti preoccuparti se è inutile: se è IDisposable, è tuo compito Dispose () it.
Marc Gravell

4
Questo non è vero per la classe StreamWriter: eliminerà il flusso connesso solo se elimini StreamWriter - non eliminerà mai il flusso se viene raccolto dalla spazzatura e il suo finalizzatore viene chiamato - questo è di progettazione.
springy76

4
So che questa domanda era del 2008, ma oggi abbiamo la libreria di attività .NET 4.0. Dispose () non è necessario nella maggior parte dei casi quando si utilizza Tasks. Anche se sono d'accordo che IDisposable dovrebbe significare " Faresti meglio a smaltirlo quando hai finito", non lo significa più.
Phil,

7
Un altro esempio per cui non dovresti disporre di un oggetto IDisposable è HttpClient aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong Solo un altro esempio di BCL in cui è presente un oggetto IDisposable e non hai bisogno (o addirittura non dovresti) smaltire esso. Questo solo per ricordare che di solito ci sono alcune eccezioni alla regola generale, anche in BCL;)
Mariusz Pawelski

166

Non trapelerai nulla, almeno nell'attuale implementazione.

La chiamata a Dispose non pulirà più velocemente la memoria utilizzata da MemoryStream. Esso sarà interrompere il flusso di essere valida per le chiamate di lettura / scrittura dopo la chiamata, che può o non può essere utile a voi.

Se sei assolutamente sicuro di non voler mai passare da un MemoryStream a un altro tipo di flusso, non ti farà alcun male non chiamare Dispose. Tuttavia, è generalmente buona pratica in parte perché se mai fare il cambiamento di utilizzare un diverso flusso, non si desidera ottenere morso da un bug difficile da trovare perché si è scelto la via più facile nella fase iniziale. (D'altra parte, c'è l'argomento YAGNI ...)

L'altro motivo per farlo comunque è che una nuova implementazione potrebbe introdurre risorse che verrebbero liberate su Dispose.


In questo caso, la funzione restituisce un MemoryStream perché fornisce "dati che possono essere interpretati in modo diverso a seconda dei parametri di chiamata", quindi avrebbe potuto essere un array di byte, ma per altri motivi è stato più semplice farlo come MemoryStream. Quindi sicuramente non sarà un'altra classe Stream.
Coderer

In tal caso, proverei comunque a smaltirlo solo per principio generale - costruire buone abitudini ecc. - Ma non mi preoccuperei troppo se diventasse complicato.
Jon Skeet

1
Se uno è veramente preoccupato di liberare risorse al più presto, annulla il riferimento immediatamente dopo il blocco "using", in modo che le risorse non gestite (se ce ne siano) vengono ripulite e l'oggetto diventa idoneo per la garbage collection. Se il metodo ritorna immediatamente, probabilmente non farà molta differenza, ma se continui a fare altre cose nel metodo come richiedere più memoria, allora sicuramente potrebbe fare la differenza.
Triynko

@Triynko Non proprio vero: vedi: stackoverflow.com/questions/574019/… per i dettagli.
George Stocker

10
L'argomento YAGNI potrebbe essere interpretato in entrambi i modi: poiché decidere di non smaltire qualcosa che implementa IDisposableè un caso speciale che va contro le normali best practice, potresti sostenere che è quel caso che non dovresti farlo fino a quando non ne hai davvero bisogno, sotto lo YAGNI principio.
Jon Hanna

26

Sì, c'è una perdita , a seconda di come definisci LEAK e di quanto LATER intendi ...

Se per perdita intendi "la memoria rimane allocata, non disponibile per l'uso, anche se hai finito di usarla" e con quest'ultima intendi in qualsiasi momento dopo aver chiamato dispose, allora sì, potrebbe esserci una perdita, sebbene non sia permanente (cioè per la durata del runtime delle applicazioni).

Per liberare la memoria gestita utilizzata da MemoryStream, è necessario annullarne il riferimento, annullando il riferimento ad essa, in modo che diventi immediatamente idoneo per la garbage collection. Se non lo fai, crei una perdita temporanea dal momento in cui hai finito di usarlo, fino a quando il tuo riferimento esce dall'ambito, perché nel frattempo la memoria non sarà disponibile per l'allocazione.

Il vantaggio dell'istruzione using (rispetto alla semplice chiamata dispose) è che puoi DICHIARARE il tuo riferimento nell'istruzione using. Quando l'istruzione using termina, non solo viene chiamato dispose, ma il tuo riferimento esce dall'ambito, annullando di fatto il riferimento e rendendo immediatamente il tuo oggetto idoneo per la garbage collection senza che ti sia richiesto di ricordare di scrivere il codice "reference = null".

Sebbene non riuscire a togliere il riferimento a qualcosa subito non sia una classica perdita di memoria "permanente", ha sicuramente lo stesso effetto. Ad esempio, se mantieni il tuo riferimento a MemoryStream (anche dopo aver chiamato dispose), e un po 'più in basso nel tuo metodo provi ad allocare più memoria ... la memoria in uso dal tuo flusso di memoria ancora referenziato non sarà disponibile a te fino a quando non annulli il riferimento o non esce dall'ambito, anche se hai chiamato dispose e hai finito di usarlo.


6
Amo questa risposta. A volte le persone dimenticano il duplice dovere di utilizzare: avido recupero delle risorse e desideroso dereferencing.
Kit

1
Infatti, anche se ho sentito che a differenza di Java, il compilatore C # rileva "l'ultimo utilizzo possibile", quindi se la variabile è destinata a uscire dall'ambito dopo il suo ultimo riferimento, potrebbe diventare idonea per la raccolta dei rifiuti subito dopo il suo ultimo utilizzo possibile .. prima che vada effettivamente fuori campo. Vedere stackoverflow.com/questions/680550/explicit-nulling
Triynko

2
Il garbage collector e il jitter non funzionano in questo modo. Scope è una costruzione del linguaggio e non qualcosa a cui il runtime obbedirà. In effetti, probabilmente stai allungando il tempo in cui il riferimento è in memoria, aggiungendo una chiamata a .Dispose () quando il blocco finisce. Vedi ericlippert.com/2015/05/18/…
Pablo Montilla

8

La chiamata .Dispose()(o il wrapping con Using) non è richiesta.

Il motivo per cui chiami .Dispose()è rilasciare la risorsa il prima possibile .

Pensa in termini, ad esempio, al server Stack Overflow, in cui abbiamo un set limitato di memoria e migliaia di richieste in arrivo. Non vogliamo aspettare la garbage collection pianificata, vogliamo rilasciare quella memoria il prima possibile in modo che sia disponibile per nuove richieste in arrivo.


24
Tuttavia, la chiamata di Dispose su un MemoryStream non rilascerà alcuna memoria. In effetti, puoi ancora ottenere i dati in un MemoryStream dopo aver chiamato Dispose - provalo :)
Jon Skeet

12
-1 Anche se è vero per un MemoryStream, come consiglio generale questo è semplicemente sbagliato. Dispose consiste nel rilasciare risorse non gestite , come handle di file o connessioni al database. La memoria non rientra in quella categoria. È quasi sempre necessario attendere la raccolta dei dati obsoleti pianificata per liberare memoria.
Joe

1
Qual è il vantaggio di adottare uno stile di codifica per l'allocazione e la disposizione degli FileStreamoggetti e uno diverso per gli MemoryStreamoggetti?
Robert Rossney

3
Un FileStream coinvolge risorse non gestite che potrebbero essere effettivamente liberate immediatamente dopo aver chiamato Dispose. Un MemoryStream, d'altra parte, archivia un array di byte gestito nella sua variabile _buffer, che non viene liberata al momento dello smaltimento. In effetti, il _buffer non viene nemmeno annullato nel metodo Dispose di MemoryStream, che è un IMO BUG VERGOGNOSO perché l'annullamento del riferimento potrebbe rendere la memoria idonea per GC proprio al momento dello smaltimento. Invece, un riferimento MemoryStream persistente (ma eliminato) mantiene ancora la memoria. Pertanto, una volta smaltito, dovresti anche annullarlo se è ancora nell'ambito.
Triynko

@Triynko - "Pertanto, una volta smaltito, dovresti anche annullarlo se è ancora nell'ambito" - Non sono d'accordo. Se viene utilizzato di nuovo dopo la chiamata a Dispose, verrà generata un'eccezione NullReferenceException. Se non viene utilizzato di nuovo dopo Dispose, non è necessario annullarlo; il GC è abbastanza intelligente.
Joe

8

Questo ha già una risposta, ma aggiungerò solo che il buon vecchio principio di nascondere le informazioni significa che potresti in futuro voler eseguire il refactoring:

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

per:

Stream foo()
{    
   ...
}

Ciò sottolinea che ai chiamanti non dovrebbe interessare quale tipo di Stream viene restituito e rende possibile modificare l'implementazione interna (ad esempio, quando si prende in giro per test di unità).

Dovrai quindi essere nei guai se non hai usato Dispose nella tua implementazione della barra:

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

5

Tutti i flussi implementano IDisposable. Avvolgi il tuo flusso di memoria in una dichiarazione di utilizzo e starai bene e dandy. Il blocco using garantirà la chiusura e l'eliminazione del flusso, qualunque cosa accada.

ovunque tu chiami Foo puoi farlo usando (MemoryStream ms = foo ()) e penso che dovresti comunque stare bene.


1
Un problema che ho riscontrato con questa abitudine è che devi essere sicuro che lo stream non venga utilizzato da nessun'altra parte. Ad esempio, ho creato un JpegBitmapDecoder puntato a un MemoryStream e ho restituito Frames [0] (pensando che avrebbe copiato i dati nel proprio archivio interno) ma ho scoperto che la bitmap sarebbe stata visualizzata solo il 20% delle volte - si è scoperto che era perché Stavo smaltendo il flusso di memoria.
devios1

Se il tuo flusso di memoria deve persistere (cioè un blocco using non ha senso), dovresti chiamare Dispose e impostare immediatamente la variabile su null. Se lo smaltisci, non è più pensato per essere utilizzato, quindi dovresti anche impostarlo su null immediatamente. Ciò che chaiguy sta descrivendo suona come un problema di gestione delle risorse, perché non dovresti distribuire un riferimento a qualcosa a meno che la cosa a cui lo stai consegnando non si assuma la responsabilità di smaltirlo, e la cosa che distribuisce il riferimento sa di non essere più responsabile così facendo.
Triynko

2

Non perderai memoria, ma il revisore del codice è corretto per indicare che dovresti chiudere lo stream. È educato farlo.

L'unica situazione in cui potresti perdere memoria è quando lasci accidentalmente un riferimento al flusso e non lo chiudi mai. Ancora non sono realmente che perde la memoria, ma si sta inutilmente estendere la quantità di tempo che si pretende di essere usarlo.


1
> Non stai ancora perdendo memoria, ma stai estendendo inutilmente la quantità di tempo che affermi di usarla. Sei sicuro? Dispose non rilascia memoria e chiamarlo in ritardo nella funzione potrebbe effettivamente estendere il tempo in cui non può essere raccolto.
Jonathan Allen

2
Sì, Jonathan ha ragione. Effettuare una chiamata a Dispose in una fase avanzata della funzione potrebbe effettivamente indurre il compilatore a pensare che sia necessario accedere all'istanza del flusso (per chiuderla) molto tardi nella funzione. Questo potrebbe essere peggio che non chiamare affatto dispose (quindi evitare un riferimento tardo nella funzione alla variabile stream), poiché un compilatore potrebbe altrimenti calcolare un punto di rilascio ottimale (noto anche come "punto di ultimo utilizzo possibile") prima nella funzione .
Triynko

2

Suggerirei avvolgendo il MemoryStream in bar()in una usingdichiarazione soprattutto per la consistenza:

  • In questo momento MemoryStream non libera la memoria .Dispose() , ma è possibile che in futuro possa farlo, o tu (o qualcun altro nella tua azienda) potresti sostituirlo con il tuo MemoryStream personalizzato che lo fa, ecc.
  • È utile stabilire uno schema nel progetto per garantire che tutti gli stream vengano eliminati: la linea viene tracciata più saldamente dicendo "tutti gli stream devono essere eliminati" invece di "alcuni stream devono essere eliminati, ma alcuni non devono farlo" ...
  • Se cambi il codice per consentire la restituzione di altri tipi di stream, dovrai cambiarlo per smaltirlo comunque.

Un'altra cosa che faccio di solito nei casi come foo()quando creo e restituisco un IDisposable è assicurarmi che qualsiasi errore tra la costruzione dell'oggetto e quello returnvenga intercettato da un'eccezione, elimini l'oggetto e rilanci l'eccezione:

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

1

Se un oggetto implementa IDisposable, devi chiamare il metodo .Dispose quando hai finito.

In alcuni oggetti, Dispose ha lo stesso significato di Chiudi e viceversa, in tal caso, uno dei due è buono.

Ora, per la tua domanda particolare, no, non perderai la memoria.


3
"Must" è una parola molto forte. Ogni volta che ci sono regole, vale la pena conoscere le conseguenze della loro violazione. Per MemoryStream, ci sono pochissime conseguenze.
Jon Skeet

-1

Non sono un esperto di .net, ma forse il problema qui sono le risorse, ovvero l'handle del file e non la memoria. Immagino che il garbage collector alla fine libererà il flusso e chiuderà l'handle, ma penso che sarebbe sempre buona pratica chiuderlo esplicitamente, per assicurarti di svuotare il contenuto su disco.


Un MemoryStream è tutto in memoria: non c'è nessun handle di file qui.
Jon Skeet

-2

Lo smaltimento delle risorse non gestite non è deterministico nelle lingue raccolte in Garbage Collection. Anche se chiami Dispose in modo esplicito, non hai assolutamente alcun controllo su quando la memoria di supporto viene effettivamente liberata. Dispose viene chiamato implicitamente quando un oggetto esce dall'ambito, sia uscendo da un'istruzione using sia facendo apparire lo stack di chiamate da un metodo subordinato. Detto questo, a volte l'oggetto può effettivamente essere un wrapper per una risorsa gestita (ad esempio un file). Questo è il motivo per cui è buona pratica chiudere esplicitamente le istruzioni finalmente o utilizzare l'istruzione using. Saluti


1
Non esattamente vero. Dispose viene chiamato quando si esce da un'istruzione using. Dispose non viene chiamato quando un oggetto esce dall'ambito.
Alexander Abramov

-3

MemorySteram non è altro che un array di byte, che è un oggetto gestito. Dimenticare di smaltire o chiudere questo non ha alcun effetto collaterale se non quello della finalizzazione.
Basta controllare il constuctor o il metodo di flush di MemoryStream nel riflettore e sarà chiaro perché non è necessario preoccuparsi di chiuderlo o eliminarlo se non per seguire la buona pratica.


6
-1: Se hai intenzione di postare su una domanda vecchia di 4 anni con una risposta accettata, prova a renderla utile.
Tieson T.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.