2010-04-27 13 views
6

Certamente dovremmo chiamare Dispose() su oggetti IDisposable non appena non ne abbiamo bisogno (che spesso è semplicemente lo scopo di un'istruzione "using"). Se non prendiamo questa precauzione, potrebbero accadere cose brutte, da impercettibili a show-stop.C'è un momento in cui ignorare IDisposable.Dispose?

Ma per quanto riguarda "l'ultimo momento" prima della fine del processo? Se i tuoi IDisposables non sono stati esplicitamente disposti in quel momento, non è vero che non conta più? Chiedo perché le risorse non gestite, al di sotto del CLR, sono rappresentate da oggetti del kernel e la terminazione del processo win32 libererà comunque tutte le risorse non gestite/gli oggetti del kernel. Detto diversamente, nessuna risorsa rimarrà "trapelata" dopo la fine del processo (indipendentemente dal fatto che Dispose() sia stato chiamato su IDisposables persistenti).

Qualcuno può pensare a un caso in cui la chiusura del processo lascerebbe ancora una risorsa trapelata, semplicemente perché Dispose() non è stato esplicitamente chiamato su uno o più IDisposables?

Si prega di non fraintendere questa domanda: non sto cercando di giustificare ignorando IDisposables. La domanda è solo teorico-tecnica.

EDIT: E che dire di mono in esecuzione su Linux? La cessazione del processo è altrettanto "affidabile" nel ripulire le "perdite" non gestite?

EDIT LATE: Sebbene possano esistere "altri usi" per IDisposables, il mio obiettivo è strettamente relativo alle perdite di risorse. Ho sentito due risposte: (1) se il processo si rifiuta di terminare, si avrà una perdita e (2) sì, le risorse possono perdere anche se il processo termina. Sono certamente d'accordo con l'articolo (1), anche se è al di fuori della portata di ciò che sto cercando. Altrimenti, l'oggetto (2) è esattamente quello che sto cercando, ma non posso scuotere la sensazione che sia solo un'ipotesi. Jeffrey Richter ("Windows via C/C++") spiega che un processo Win32 terminato (con grazia) non lascerà le risorse perdute o orfane. Perché un processo contenente il CLR lo cambierebbe? Dove sono la documentazione, l'esempio specifico o lo scenario teorico che dà credibilità all'idea che la capacità di pulizia del processo Win32 è compromessa quando si utilizza il CLR?

+0

Il finalizzatore è sempre lì per prendersi cura delle risorse non gestite che non sono state smaltite. E se non funziona allora hai il sistema operativo per ripulire le schegge. –

risposta

2

Tecnicamente parlando, tutto dipende da ciò che fa l'IDisposable. È stato usato per un sacco di cose, non solo risorse non gestite.

Ad esempio, quando si lavora su un'applicazione di Outlook, ho creato una piccola astrazione dell'API di Outlook. Gli allegati erano particolarmente fastidiosi con cui lavorare come flussi perché era necessario salvarlo in un file temporaneo, lavorarci sopra, quindi ripulirlo.

Quindi la mia astrazione sembrava qualcosa di simile:

OutlookMailItem mailItem = blah; 
using (Stream attachmentStream = mailItem.OpenAttachment("something.txt")) { 
    // work with stream 
} 

Quando Dispose è stato chiamato sul AttachmentStream, il file temporaneo si basava su è stato eliminato. In questo caso, se Dispose non è stato chiamato, il file temporaneo non verrebbe mai ripulito. All'inizio ho avuto un processo per cercare questi file orfani ma ho pensato di presentarlo come esempio.

In realtà, quasi tutte le implementazioni IDisposable che avvolgono una sorta di socket, handle o transazione verranno semplicemente ripulite dal sistema operativo al termine del processo. Ma ovviamente è come il benessere. Evitalo se puoi.

+0

Concordato, in genere non dobbiamo fare affidamento sul benessere della pulitura della terminazione del processo Win32. Ma sembra che la terminazione del processo Win32 consenta di liberare tutte le risorse rimanenti che IDisposables * dovrebbe * aver gestito. Sto iniziando a pensare che il tuo post sia la mia risposta. Pensieri? –

+0

Ci sono davvero due parti per quella domanda. Il primo riguarda il framework. Non garantisce che Dispose sarà chiamato ma fa il miglior tentativo. La seconda parte della tua domanda è cosa succede se i finalizzatori non vengono eseguiti. Ed è qui che "dipende". Windows ha integrato la pulizia delle maniglie e così via. Quando l'IDisposable avvolge una di queste risorse, Windows si ripulirà indipendentemente da ciò che ha fatto il GC. Ma se l'IDisposable fa qualcosa di strano (come il mio esempio di cancellazione di un file temporaneo), allora è possibile che la pulizia non avvenga mai. – Josh

1

Una cosa che spesso mi trovo a smaltire sono le porte seriali - ora, mentre una porta seriale dovrebbe essere liberata quando il programma lo lascia, altri programmi non possono accedere alla porta seriale mentre è trattenuto da un altro processo. Quindi, se il tuo processo si rifiuta di morire, allora stai legando una porta seriale. Questo può essere davvero brutto se l'utente tenta di riavviare il programma, ma una versione zombie del processo precedente è ancora in attesa sulla porta seriale.

E sì, il mio programma si è trovato prima in uno stato zombie, quindi il cliente si lamenta che il programma non funziona più perché il programma non riesce a collegarsi alla porta seriale al riavvio. Il risultato è di far passare l'utente uccidendo un processo nel task manager o riavviandolo, nessuno dei quali è un compito particolarmente facile da usare.

+1

Non utilizzare Invoke() nel gestore di eventi DataReceived, è soggetto a deadlock della chiamata Close(). Utilizzare invece BeginInvoke(). –

+0

Sono abbastanza sicuro che la zombificazione avvenga altrove; Non uso il gestore di eventi DataReceived perché il dispositivo a cui sto collegando è instabile e non ha un carattere di arresto appropriato. Invece, invia un checksum del comando come carattere di stop, e poiché il checksum è sempre diverso, devo costruire la stringa un carattere alla volta. È lancinante e chiaramente progettato da qualcuno nell'hardware, non nel software. – mmr

0

Scrivi il tuo codice personalizzato per liberare oggetti nella tua classe usa e getta. È per te scrivere codice per liberare codice non gestito e gestito.

Questo è il motivo per cui è necessaria la funzione Dispose. Tutto il free-up della memoria non è automatico, come hai detto nel caso del codice non gestito.

Ora, se si pensa che il codice non gestito venga automaticamente liberato dal sistema operativo, non è vero. Esistono molti handle e blocchi che possono rimanere attivi se non correttamente disposti dall'applicazione.

0

Innanzitutto, vorrei sottolineare che IDisposable non è solo per gli oggetti del kernel di Windows o la memoria non gestita. Piuttosto, è per le cose che la garbage collection non capisce (di cui gli oggetti del kernel sono un caso speciale).

Un piccolo esempio, giusto per dare un'idea:

sealed class Logger : IDisposable 
    { 
     private bool _disposed = false; 

     public Logger() 
     { 
      System.IO.File.AppendAllText(@"C:\mylog.txt", "LOG STARTED"); 
     } 

     ~Logger() 
     { 
      Dispose(); 
     } 

     public void Dispose() 
     { 
      if (!_disposed) 
      { 
       System.IO.File.AppendAllText(@"C:\mylog.txt", "LOG STOPPED"); 
       _disposed = true; 
      } 
     } 

     public void WriteMessage(string msg) 
     { 
      System.IO.File.AppendAllText(@"C:\mylog.txt", "MESSAGE: " + msg); 
     } 
    } 

Si noti che questa classe Logger non "hold" tutte le risorse-oggetto-stile kernel. Apre il file e lo chiude subito. Tuttavia, c'è questa logica dietro che dovrebbe scrivere "LOG STOPPED" quando l'oggetto viene distrutto. Questa logica è qualcosa che GC non riesce a capire - e questo è il posto in cui usare IDisposable.

Pertanto, non è possibile contare su Windows dopo aver eliminato gli oggetti del kernel. Potrebbe ancora esserci qualcosa anche se Windows non lo sa.

Detto questo, tuttavia, a condizione che gli oggetti monouso siano scritti correttamente (vale a dire chiamando Dispose nel finalizzatore), saranno ancora eliminati dal CLR all'uscita del processo.

Quindi, la risposta è: sì, non è necessario chiamare Dispose subito prima dell'uscita del progetto. Ma NON perché le risorse usa e getta sono oggetti del kernel (perché non lo sono necessariamente).


Modifica

Come Josh Einstein giustamente osservato, finalizzatori sono in realtà non garantiti per funzionare su uscita del processo. Ecco che allora la risposta diventa: chiamano sempre Dispose, solo per essere citare in giudizio

+0

"saranno ancora eliminati dal CLR all'uscita del processo" I finalizzatori non sono garantiti per l'esecuzione alla fine del processo e, a meno che non siano pochi e finiscano rapidamente, è molto probabile che vengano interrotti se addirittura chiamati. – Josh

+0

Trovato alcune dichiarazioni di supporto oltre al mio commento sopra. I timeout sono più lunghi di quanto pensassi, ma il CLR imporrà ancora i timeout del finalizzatore che sarebbero particolarmente evidenti se si esegue l'I/O nel finalizzatore. http://nitoprograms.blogspot.com/2009/08/finalizers-at-process-exit.html – Josh

+0

Sì, hai ragione, me ne sono completamente dimenticato. –

2

Durante un arresto cooperativa, il dominio di applicazione viene scaricata, che fa sì che tutti i finalizzatori per eseguire:

Da Object.Finalize documentazione:

Durante l'arresto di un dominio dell'applicazione, Finalize viene richiamato automaticamente sugli oggetti che non sono esenti dalla finalizzazione, anche quelli che sono ancora accessibili.

Quindi sei sicuro su arresto fino a due criteri sono soddisfatti:

  • Ogni IDisposable oggetto che è ancora vivo ha un finalizzatore correttamente attuata (vero classi del framework, non può essere vero di librerie meno affidabili); e

  • Si tratta in realtà di uno spegnimento cooperativo e non di uno spegnimento anomalo come una terminazione del processo difficile, Ctrl-C in un'app console o Environment.FailFast.

Se uno di questi due criteri non sono soddisfatti, è possibile che l'applicazione è trattenendo risorse non gestite globali (come ad esempio un mutex), che in realtà perderà. Pertanto, è sempre meglio chiamare il Dispose in anticipo se puoi. Più del del tempo, puoi fare affidamento sul CLR e sui finalizzatori di oggetti per fare questo lavoro per te, ma è meglio prevenire che curare.

+0

Solo un avvertimento: non è possibile chiamare i finalizzatori durante la chiusura del processo. Il CLR farà un tentativo ma non dovrebbe essere previsto. Il comportamento corrente ha limiti di tempo per finalizzatore e per la terminazione generale. Questo post contiene alcune dichiarazioni di supporto: http://nitoprograms.blogspot.com/2009/08/finalizers-at-process-exit.html – Josh

+0

@Josh: molto vero. Certo, nulla è mai garantito; se togli la spina, * niente * sta per essere ripulito. ;) – Aaronaught

+0

Oltre al commento di Josh riportato sopra, ci sono circostanze in cui verranno chiamati * no * finalizzatori, come ad esempio invocare 'System.Environment.FailFast' (questo è sostanzialmente l'equivalente .Net di kill -9 su * nix). –

Problemi correlati