Cosa succede se non chiamo Smaltire sull’object penna?

Cosa succede se non chiamo Dispose sull’object pen in questo snippet di codice?

 private void panel_Paint(object sender, PaintEventArgs e) { var pen = Pen(Color.White, 1); //Do some drawing } 

La Pen verrà ritirata dal GC in un punto imprecisato in futuro, indipendentemente dal fatto che chiami o meno Dispose .

Tuttavia, le risorse non gestite detenute dalla penna (ad esempio un handle GDI +) non verranno eliminate dal GC. Il GC pulisce solo le risorse gestite. Calling Pen.Dispose consente di garantire che queste risorse non gestite vengano eliminate in modo tempestivo e che non si stiano perdendo risorse.

Ora, se la Pen ha un finalizzatore e quel finalizzatore pulisce le risorse non gestite, quelle dette risorse non gestite verranno ripulite quando la Pen viene raccolta. Ma il punto è che:

  1. È necessario chiamare Dispose modo esplicito in modo da rilasciare le risorse non gestite e
  2. Non dovresti aver bisogno di preoccuparti dei dettagli di implementazione se c’è un finalizzatore e pulisce le risorse non gestite.

Pen implementabile IDisposable . IDisposable è per lo smaltimento di risorse non gestite. Questo è lo schema in .NET.

Per i commenti precedenti su questo argomento, vedere questa risposta .

Un paio di correzioni dovrebbero essere fatte qui:

Per quanto riguarda la risposta di Phil Devaney:

“… Calling Dispose ti consente di eseguire una pulizia deterministica ed è altamente raccomandato.”

In realtà, chiamare Dispose () non determina in modo deterministico una raccolta GC in .NET, vale a dire che NON triggers immediatamente un GC solo perché hai chiamato Dispose (). Segnala indirettamente al GC che l’object può essere ripulito durante il prossimo GC (per la Generazione in cui l’object vive). In altre parole, se l’object risiede in Gen 1, non verrà eliminato finché non verrà eseguita una raccolta Gen 1. Uno degli unici modi (anche se non i soli) che è ansible determinare in modo programmatico e deterministico dal GC per eseguire una raccolta è chiamando GC.Collect (). Tuttavia, non è consigliabile farlo poiché il GC “si sintonizza” da solo durante il runtime raccogliendo le metriche relative alle allocazioni di memoria durante il runtime per la tua app. Chiamando GC.Collect () si scaricano queste metriche e si fa ricominciare da capo il GC.

Per quanto riguarda la risposta:

IDisposable è per lo smaltimento di risorse non gestite . Questo è lo schema in .NET.

Questo è incompleto. Poiché il GC non è deterministico, il modello Dispose, ( Come implementare correttamente il pattern Dispose ), è disponibile in modo da poter rilasciare le risorse che si stanno utilizzando, gestite o non gestite. Non ha nulla a che fare con il tipo di risorse che stai rilasciando. La necessità di implementare un Finalizer ha a che fare con il tipo di risorse che si sta utilizzando, ad esempio implementando SOLTANTO uno se si dispone di risorse non definibili (cioè native). Forse stai confondendo i due. A proposito, dovresti evitare di implementare un Finalizer usando invece la class SafeHandle che avvolge le risorse native che vengono sottoposte a marshalling tramite P / Invoke o COM Interop. Se finisci per implementare un Finalizer, devi sempre implementare il pattern di smaltimento.

Una nota critica che non ho ancora visto nessuno menziona è che se l’object usa e getta viene creato e ha un Finalizzatore (e non si sa mai davvero se lo fanno) e certamente non si dovrebbe fare alcuna ipotesi al riguardo, allora sarà essere inviato direttamente alla coda di finalizzazione e vivere per almeno 1 raccolta GC extra .

Se GC.SuppressFinalize () non viene infine chiamato, il finalizzatore per l’object verrà chiamato al prossimo GC. Si noti che un’implementazione corretta del modello Dispose dovrebbe chiamare GC.SuppressFinalize (). Pertanto, se si chiama Dispose () sull’object e il modello è stato implementato correttamente, si eviterà l’esecuzione di Finalizer. Se non chiami Dispose () su un object che ha un finalizzatore, l’object avrà il suo Finalizer eseguito dal GC sulla prossima raccolta. Perché questo è cattivo? Il thread di Finalizer nel CLR fino ae incluso .NET 4.6 è a thread singolo. Immagina cosa succede se aumenti il ​​carico su questo thread: la tua performance delle app va a sapere dove.

La chiamata Disponi su un object prevede quanto segue:

  1. ridurre la tensione sul GC per il processo;
  2. ridurre la pressione della memoria dell’app;
  3. ridurre la possibilità di una OutOfMemoryException (OOM) se il LOH (Large Object Heap) viene frammentato e l’object si trova sul LOH;
  4. Tenere l’object fuori dalle Code Finalizzabili e f-raggiungibili se ha un Finalizzatore;
  5. Assicurati che le tue risorse (gestite e non gestite) vengano pulite in modo deterministico.

Edit : Ho appena notato che la documentazione MSDN “tutti sapendo e sempre corretta” su IDisposable (estremo sarcasmo qui) in realtà dice

L’uso principale di questa interfaccia è di rilasciare risorse non gestite

Come qualcuno dovrebbe sapere, MSDN è tutt’altro che corretto, non menziona mai o mostra le “migliori pratiche”, a volte fornisce esempi che non vengono compilati, ecc. È un peccato che questo sia documentato in quelle parole. Tuttavia, so cosa stavano cercando di dire: in un mondo perfetto il GC pulirà tutte le risorse gestite per te (quanto idealista); tuttavia, non ripulirà le risorse non gestite . Questo è assolutamente vero. Detto questo, la vita non è perfetta e nessuna delle due è un’applicazione. Il GC pulirà solo le risorse che non hanno riferimenti di root. Questo è principalmente il problema.

Tra circa 15-20 diversi modi in cui .NET può “perdere” (o non liberare) la memoria, quella che molto probabilmente ti morderebbe se non chiami Dispose () è l’errore di annullare la registrazione / unhook / unwire / detach dell’evento telescopici / delegati. Se crei un object che ha dei delegati collegati e non chiami Dispose () (e non scollega i delegati tu stesso), il GC vedrà comunque l’object come avente riferimenti di root, cioè i delegati. Pertanto, il GC non lo raccoglierà mai.

@ commento / domanda di joren qui sotto (la mia risposta è troppo lunga per un commento):

Ho un post sul blog sullo schema di Dispose che consiglio di usare – ( Come implementare correttamente il pattern Dispose ). Ci sono momentjs in cui dovresti annullare i riferimenti e non fa mai male farlo. In realtà, fare ciò fa qualcosa prima dell’esecuzione di GC – rimuove il riferimento con radice a quell’object. Successivamente GC analizza la sua raccolta di riferimenti root e raccoglie quelli che non hanno un riferimento root. Pensa a questo esempio quando è opportuno farlo: hai un’istanza di tipo “ClassA” – chiamiamola “X”. X contiene un object di tipo “ClassB”, chiamiamolo “Y”. Y implementa IDisposable, quindi, X dovrebbe fare lo stesso per disporre di Y. Supponiamo che X sia in generazione 2 o LOH e Y sia in generazione 0 o 1. Quando Dispose () è chiamato su X e tale implementazione annulla la riferimento a Y, il riferimento con radice a Y viene immediatamente rimosso. Se un GC si verifica per Gen 0 o Gen 1, la memoria / risorse per Y viene ripulita, ma la memoria / risorse per X non lo è poiché X vive in Gen 2 o LOH.

L’handle della penna GDI + sottostante non verrà rilasciato fino a un certo lasso di tempo indeterminato nel futuro, ad esempio quando l’object Penna viene sottoposto a Garbage Collection e viene chiamato il finalizzatore dell’object. Questo potrebbe non essere fino a quando il processo non termina, o potrebbe essere prima, ma il punto è il suo non deterministico. Calling Dispose consente di eseguire una pulizia deterministica ed è altamente raccomandato.

La quantità totale di. Netto di memoria in uso è la parte .Net + tutti i dati “esterni” in uso. Gli oggetti del SO, i file aperti, il database e le connessioni di rete prendono tutti delle risorse che non sono oggetti puramente .Net.

La grafica usa penne e altri oggetti che sono in realtà oggetti del sistema operativo che sono “abbastanza” costosi da mantenere. (Puoi scambiare la tua penna con un file bitmap 1000×1000). Questi oggetti del sistema operativo vengono rimossi dalla memoria del sistema operativo solo dopo aver chiamato una funzione di pulizia specifica. Le funzioni Penna e Bitmap Dispose lo fanno immediatamente quando le chiami.

Se non chiami Dispose, il garbage collector verrà a ripulirlo “da qualche parte in futuro *”. (In realtà chiamerà il codice destructor / finalize che probabilmente chiama Dispose ())

* su una macchina con memoria infinita (o superiore a 1 GB) da qualche parte nel futuro può essere molto lontano nel futuro. Su una macchina che non fa nulla, può essere facilmente più lungo di 30 minuti per pulire quell’enorme bitmap o una penna molto piccola.

Se vuoi davvero sapere quanto è grave quando non chiami Dispose sugli oggetti grafici, puoi usare il CLR Profiler, disponibile gratuitamente per il download qui. Nella cartella di installazione (di default C: \ CLRProfiler) è CLRProfiler.doc che ha un bell’esempio di cosa succede quando non si chiama Dispose su un object Brush. È molto illuminante. Si consiglia inoltre di leggere su utilizzando IDisposable qui e qui .

Manterrà le risorse fino a quando il garbage collector lo pulirà

Dipende se implementa il finalizzatore e chiama il Dispose sul suo metodo finalize. In tal caso, l’handle verrà rilasciato da GC.

in caso contrario, l’handle rimarrà in sospeso fino al termine del processo.

Con le cose grafiche può essere molto brutto.

Apri il Task Manager di Windows. Fai clic su “scegli colonne” e scegli la colonna denominata “Oggetti GDI”.

Se non si dispone di determinati oggetti grafici, questo numero continuerà ad aumentare e aumentare.

Nelle versioni precedenti di Windows questo può causare il crash dell’intera applicazione (il limite era 10000 per quanto ricordo), non è sicuro di Vista / 7, ma è comunque una brutta cosa.

il garbage collector lo raccoglierà comunque, ma è importante QUANDO: se non si chiama disporre su un object che non lo si usa vivrà più a lungo in memoria e verrà promosso a generazioni più elevate, il che significa che raccoglierlo ha un costo maggiore.

nella mia mente la prima idea è venuta alla superficie è che questo object sarà eliminato non appena il metodo termina l’esecuzione !, non so dove ho ottenuto queste informazioni!, è giusto?