Aggiornamento (1 dicembre 2009):
Vorrei modificare questa risposta e ammettere che la risposta originale era difettosa.
L'analisi originale si applica agli oggetti che richiedono la finalizzazione e il punto che le pratiche non dovrebbero essere accettate in superficie senza una comprensione accurata e approfondita rimane valido.
Tuttavia, si scopre che DataSet, DataViews, DataTables sopprimono la finalizzazione nei loro costruttori - questo è il motivo per cui chiamare Dispose () su di essi esplicitamente non fa nulla.
Presumibilmente, ciò accade perché non dispongono di risorse non gestite; quindi nonostante il fatto che MarshalByValueComponent tenga conto delle risorse non gestite, queste implementazioni particolari non hanno la necessità e possono quindi rinunciare alla finalizzazione.
(Che gli autori di .NET si preoccuperebbero di sopprimere la finalizzazione sui tipi che normalmente occupano la maggior parte della memoria parla dell'importanza di questa pratica in generale per i tipi finalizzabili.)
Ciononostante, questi dettagli sono ancora sotto documentati poiché la nascita di .NET Framework (quasi 8 anni fa) è piuttosto sorprendente (che in sostanza sei lasciato ai tuoi dispositivi per setacciare materiale ambiguo ma contrastante per mettere insieme i pezzi a volte è frustrante ma fornisce una comprensione più completa del quadro su cui facciamo affidamento ogni giorno).
Dopo molte letture, ecco la mia comprensione:
Se un oggetto richiede la finalizzazione, potrebbe occupare memoria più a lungo del necessario - ecco perché: a) Qualsiasi tipo che definisce un distruttore (o eredita da un tipo che definisce un distruttore) è considerato finalizzabile; b) Al momento dell'allocazione (prima che venga eseguito il costruttore), un puntatore viene posizionato sulla coda di finalizzazione; c) Un oggetto finalizzabile richiede normalmente la raccolta di 2 raccolte (anziché lo standard 1); d) La soppressione della finalizzazione non rimuove un oggetto dalla coda di finalizzazione (come riportato da! FinalizeQueue in SOS) Questo comando è fuorviante; Sapere quali oggetti si trovano nella coda di finalizzazione (di per sé) non è utile; Sapere quali oggetti si trovano nella coda di finalizzazione e richiedono ancora la finalizzazione sarebbe utile (esiste un comando per questo?)
La soppressione della finalizzazione si spegne un po 'nell'intestazione dell'oggetto indicando al runtime che non è necessario richiamare il finalizzatore (non è necessario spostare la coda FReachable); Rimane nella coda di finalizzazione (e continua a essere segnalato da! FinalizeQueue in SOS)
Le classi DataTable, DataSet, DataView sono tutte radicate in MarshalByValueComponent, un oggetto finalizzabile che può (potenzialmente) gestire risorse non gestite
- Poiché DataTable, DataSet, DataView non introducono risorse non gestite, eliminano la finalizzazione nei loro costruttori
- Sebbene questo sia un modello insolito, libera il chiamante dal doversi preoccupare di chiamare Dispose dopo l'uso
- Questo e il fatto che DataTables possono potenzialmente essere condivisi tra diversi DataSet, è probabilmente il motivo per cui i DataSet non si preoccupano di smaltire DataTables figlio
- Questo significa anche che questi oggetti appariranno sotto! FinalizeQueue in SOS
- Tuttavia, questi oggetti dovrebbero comunque essere recuperabili dopo una singola raccolta, come le loro controparti non finalizzabili
4 (nuovi riferimenti):
Risposta originale:
Ci sono molte risposte fuorvianti e generalmente molto povere su questo - chiunque sia atterrato qui dovrebbe ignorare il rumore e leggere attentamente i riferimenti qui sotto.
Senza dubbio, Dispose dovrebbe essere chiamato su qualsiasi oggetto Finalizable.
Le tabelle dei dati sono finalizzabili.
La chiamata a Dispose accelera notevolmente il recupero della memoria.
MarshalByValueComponent chiama GC.SuppressFinalize (this) nel suo Dispose () - saltare questo significa dover aspettare dozzine se non centinaia di raccolte Gen0 prima che la memoria venga recuperata:
Con questa comprensione di base della finalizzazione possiamo già dedurre alcune cose molto importanti:
Innanzitutto, gli oggetti che necessitano di finalizzazione vivono più a lungo degli oggetti che non lo fanno. In effetti, possono vivere molto più a lungo. Ad esempio, supponiamo che un oggetto in gen2 debba essere finalizzato. La finalizzazione verrà programmata ma l'oggetto è ancora in gen2, quindi non verrà nuovamente raccolto fino alla successiva raccolta gen2. Potrebbe essere davvero molto tempo e, in effetti, se le cose vanno bene, ci vorrà molto tempo, perché le raccolte gen2 sono costose e quindi vogliamo che accadano molto raramente. Gli oggetti più vecchi che necessitano di finalizzazione potrebbero dover attendere dozzine se non centinaia di raccolte gen0 prima che il loro spazio venga recuperato.
In secondo luogo, gli oggetti che necessitano di finalizzazione causano danni collaterali. Poiché i puntatori agli oggetti interni devono rimanere validi, non solo gli oggetti che necessitano direttamente di finalizzazione rimarranno nella memoria, ma tutto ciò a cui si riferisce l'oggetto, direttamente e indirettamente, rimarrà anche nella memoria. Se un enorme albero di oggetti fosse ancorato da un singolo oggetto che richiedeva la finalizzazione, allora l'intero albero rimarrebbe, potenzialmente per molto tempo, come abbiamo appena discusso. È quindi importante utilizzare i finalizzatori con parsimonia e posizionarli su oggetti con il minor numero possibile di puntatori interni. Nell'esempio dell'albero che ho appena dato, puoi facilmente evitare il problema spostando le risorse che necessitano di finalizzazione su un oggetto separato e mantenendo un riferimento a quell'oggetto nella radice dell'albero.
Infine, gli oggetti che necessitano di finalizzazione creano lavoro per il thread del finalizzatore. Se il processo di finalizzazione è complesso, l'unico thread del finalizzatore impiegherà molto tempo a eseguire tali passaggi, il che può causare un arretrato di lavoro e quindi far indugiare più oggetti in attesa della finalizzazione. Pertanto, è di vitale importanza che i finalizzatori facciano il minor lavoro possibile. Ricordare inoltre che sebbene tutti i puntatori a oggetti rimangano validi durante la finalizzazione, è possibile che tali puntatori portino a oggetti che sono già stati finalizzati e potrebbero quindi essere poco utili. È generalmente più sicuro evitare i seguenti puntatori oggetto nel codice di finalizzazione anche se i puntatori sono validi. Un percorso di codice di finalizzazione sicuro e breve è il migliore.
Prendilo da qualcuno che ha visto centinaia di MB di DataTable senza riferimenti in Gen2: questo è estremamente importante e completamente mancato dalle risposte su questo thread.
Riferimenti:
1 -
http://msdn.microsoft.com/en-us/library/ms973837.aspx
2 -
http://vineetgupta.spaces.live.com/blog/cns!8DE4BDC896BEE1AD!1104.entry
http://www.dotnetfunda.com/articles/article524-net-best-practice-no-2-improve-garbage -collector-prestazioni-con-finalizedispose-pattern.aspx
3 -
http://codeidol.com/csharp/net-framework/Inside-the-CLR/Automatic-Memory-Management/