2009-11-18 12 views
31

Io di solito hanno il codice come questo in un modulo:C#: Devo disporre di un oggetto BackgroundWorker creato in fase di runtime?

private void PerformLongRunningOperation() 
    { 
     BackgroundWorker worker = new BackgroundWorker(); 

     worker.DoWork += delegate 
     { 
      // perform long running operation here 
     }; 

     worker.RunWorkerAsync(); 
    } 

Questo vuol dire che io non dispongo il BackgroundWorker, mentre se mi aveva aggiunto dal form designer, allora penso che sarebbe ottenere disposto.

Ciò causerà problemi? È più corretto dichiarare un livello di modulo _saveWorker e quindi chiamare Dispose su di esso dal metodo di smaltimento del modulo?

risposta

32

Sì, è necessario smaltire l'operatore in background.

Potrebbe risultare più semplice utilizzare ThreadPool.QueueUserWorkItem(...) che non richiede alcuna pulizia in seguito.


dettaglio aggiuntivo sul perché si dovrebbe sempre chiamare Dispose():

Anche se si guarda nella classe BackgroundWorker che in realtà non fa alcuna discussione ripulire nel suo metodo Dispose, è comunque importante chiamare Dispose a causa dell'effetto che la classe ha sul garbage collector.

Le classi con i finalizzatori non sono GCed immediatamente. Vengono mantenuti e aggiunti alla coda del finalizzatore. Quindi viene eseguito il thread di finalizzazione (che segue le chiamate di modello standard disposte). Ciò significa che l'oggetto sopravviverà nella generazione di GC 1. E le raccolte di gen 1 sono molto più rare delle raccolte di gen 0, quindi l'oggetto rimane in memoria per molto più tempo.

Se tuttavia si chiama Dispose(), l'oggetto non verrà aggiunto alla coda di finalizzazione, quindi è libero di essere raccolto.

Non è davvero un grosso problema, ma se ne stai creando molti, finirai per utilizzare più memoria del necessario. Dovrebbe essere considerata una buona pratica per chiamare sempre gli oggetti che hanno un metodo di smaltimento.

Quindi suppongo, tutto sommato, non è un requisito al 100% rigido e veloce. La tua app non esploderà (o creerà perdite di memoria) se non chiami Dispose(), ma in alcune circostanze potrebbe avere effetti negativi. Il worker in background è stato progettato per essere utilizzato come componente WinForms, quindi utilizzalo in questo modo, se hai requisiti diversi e non vuoi utilizzarlo come componente WinForms, non utilizzarlo, usa lo strumento corretto per il lavoro, come il ThreadPool.

+8

_Perché dovrebbe chiamare bgw.Dispose()? Oltre la solita logica "perché è lì". –

+0

@Henk. Ho aggiunto alcuni dettagli in più. Non è critico al 100%, ma è stato progettato per essere chiamato. Se non vuoi chiamarlo ci sono altre classi che sono più adatte per il codice non Winforms. –

6

Non mi preoccuperei, l'unica risorsa a cui può aggrapparsi Bgw è il thread e se non si dispone di un ciclo infinito nel proprio delegato, allora si sta bene.

BackgroundWorker eredita IDisposable() da Component ma non ne ha realmente bisogno.

Confrontalo per spingere il tuo metodo direttamente sul ThreadPool. Non puoi (non puoi) smaltire i fili, certamente non quelli del Pool.

Ma se il campione è completo nel senso che non si sta utilizzando l'evento Completato o le funzionalità di avanzamento/annullamento si potrebbe anche usare ThreadPool.QueueUserWorkItem().

+2

Gli oggetti thread sono molto costosi (essi, almeno, sono costituiti da una grande memoria di stack riservata, forse 1 MB). – denisenkom

+0

Sì, sono costosi. Ma come li pulirai? Inoltre, il Bgw usa il ThreadPool. –

+1

Ci sono almeno 2 buoni motivi per chiamare lo smaltimento sulla BGW. in primo luogo chiama GC.SuppressFinalize(), e in secondo luogo a causa del fatto che Wayne fa delle implementazioni future e non dipende dai dettagli di implementazione interna. –

16

La sfida è assicurarsi di disporre solo dello BackgroundWorkerdopo il è finito in esecuzione. Non è possibile farlo nell'evento Completed, poiché tale evento viene generato dallo stesso BackgroundWorker.

BackgroundWorker è destinato a essere utilizzato come componente in un modulo WinForms, quindi è consigliabile farlo o passare a qualcosa come Thread.QueueUserWorkItem. Ciò utilizzerà un thread thread-pool e non richiederà alcuna pulizia speciale al termine.

+4

+1 Poiché 'BackgroundWorker' deve essere utilizzato come componente WinForms. – Brian

2

È consigliabile chiamare Dispose() per tutti gli oggetti IDisposable. Ciò consente loro di rilasciare risorse non gestite che potrebbero contenere, come Handles. Le classi IDisposable dovrebbero anche avere finalizzatori, la cui presenza può ritardare il tempo in cui il GC è autorizzato a raccogliere completamente tali oggetti.

Se la classe alloca un IDisposable e lo assegna a una variabile membro, in generale dovrebbe essere anch'esso IDisposable.

Tuttavia, se non si chiama Dispose(), alla fine verrà chiamato il Finalizer e le risorse verranno eliminate. Il vantaggio di chiamarlo esplicitamente è che queste cose possono accadere più rapidamente e con meno spese generali, che migliorano le prestazioni delle app e riducono la pressione della memoria.

+1

"Anche le classi IDisposable dovrebbero avere i finalizzatori, la cui presenza può ritardare il tempo in cui il GC è autorizzato a raccogliere completamente tali oggetti." <- questo è vero solo se l'IDisposable ha * risorse * un * gestite per pulire – dss539

+0

Suggerisco che un oggetto di solito dovrebbe implementare solo IDisposable se si tratta di risorse non gestite; altrimenti il ​​GC fa tutto il lavoro (forse tranne nei casi in cui vorresti essere in grado di sfruttare la semantica dell'istruzione "using"). Inoltre, gli oggetti IDisposable fanno spesso riferimento ad altri IDiposables, quindi molti non gestiscono direttamente le risorse non gestite, ma piuttosto indirettamente. – RickNZ

7

A mio avviso, in generale, se è IDisposable, dovrebbe essere Dispose() d quando hai finito con esso. Anche se l'attuale implementazione di BackgroundWorker non ha bisogno di essere eliminata tecnicamente, non si vuole essere sorpresi dalle successive implementazioni interne che potrebbero.

+1

Penso che sia un buon punto per le future implementazioni. Sì, l'attuale BGW non fa nulla nel suo metodo di smaltimento, ma non c'è modo di garantire che. 4.0 4.0 BGW non ripulirà nulla. –

+1

Suona bene ma la verità è che Bgw.Dispose è solo un retaggio della sua classe base. –

+1

@henk è ** per ora **. MS non garantisce che un giorno non lo sarà, che è ** esattamente ** perché raccomandano sempre di chiamare 'Dispose()' su cose che implementano 'IDisposable'. –

3

Perché non includere una dichiarazione di utilizzo? Non molto sforzo in più e si ottiene lo smaltimento:

private void PerformLongRunningOperation() 
    { 
     using (BackgroundWorker worker = new BackgroundWorker()) 
     { 
      worker.DoWork += delegate 
          { 
           // perform long running operation here 
          }; 
      worker.RunWorkerAsync(); 
     } 
    } 

EDIT:

Ok, ho messo insieme un po 'di test per vedere un pò quello che sta succedendo con lo smaltimento e quant'altro:

using System; 
using System.ComponentModel; 
using System.Threading; 

namespace BackgroundWorkerTest 
{ 
    internal class Program 
    { 
     private static BackgroundWorker _privateWorker; 

     private static void Main() 
     { 
      PrintThread("Main"); 
      _privateWorker = new BackgroundWorker(); 
      _privateWorker.DoWork += WorkerDoWork; 
      _privateWorker.RunWorkerCompleted += WorkerRunWorkerCompleted; 
      _privateWorker.Disposed += WorkerDisposed; 
      _privateWorker.RunWorkerAsync(); 
      _privateWorker.Dispose(); 
      _privateWorker = null; 

      using (var BW = new BackgroundWorker()) 
      { 
       BW.DoWork += delegate 
           { 
            Thread.Sleep(2000); 
            PrintThread("Using Worker Working"); 
           }; 
       BW.Disposed += delegate { PrintThread("Using Worker Disposed"); }; 
       BW.RunWorkerCompleted += delegate { PrintThread("Using Worker Completed"); }; 
       BW.RunWorkerAsync(); 
      } 

      Console.ReadLine(); 
     } 

     private static void WorkerDisposed(object sender, EventArgs e) 
     { 
      PrintThread("Private Worker Disposed"); 
     } 

     private static void WorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      PrintThread("Private Worker Completed"); 
     } 

     private static void WorkerDoWork(object sender, DoWorkEventArgs e) 
     { 
      Thread.Sleep(2000); 
      PrintThread("Private Worker Working"); 
     } 

     private static void PrintThread(string caller) 
     { 
      Console.WriteLine("{0} Thread: {1}", caller, Thread.CurrentThread.ManagedThreadId); 
     } 
    } 
} 

Ecco l'output:

Main Thread: 1 
Private Worker Disposed Thread: 1 
Using Worker Disposed Thread: 1 
Private Worker Working Thread: 3 
Using Worker Working Thread: 4 
Using Worker Completed Thread: 4 
Private Worker Completed Thread: 3 

Da alcuni test, sembra che Dispose() non abbia praticamente alcun effetto su un BackgroundWorker avviato. Indipendentemente dal fatto che lo si chiami nell'ambito di un'istruzione using, o lo si utilizzi dichiarato nel codice e lo si elimini immediatamente e lo si dineferenzia, esso continua a funzionare normalmente. L'evento Disposed si verifica nel thread principale e DoWork ed RunWorkerCompleted si verificano sui thread del pool di thread (qualunque sia disponibile quando l'evento viene attivato). Ho provato un caso in cui ho annullato la registrazione dell'evento RunWorkerCompleted subito dopo aver chiamato Dispose (quindi prima che DoWork avesse la possibilità di completarlo) e RunWorkerCompleted non si attivasse. Questo mi porta a credere che tu possa ancora manipolare l'oggetto BackgroundWorker nonostante sia stato eliminato.

Così come altri hanno già detto, in questo momento sembra che chiamare Dispose non sia realmente necessario. Tuttavia, non vedo alcun danno nel farlo, almeno dalla mia esperienza e questi test.

+3

Funziona? Non disporrà troppo presto del lavoratore? – RickL

+0

L'ho usato molte volte e sicuramente "funziona" - non elimina il processo in background troppo presto. Tuttavia, non ho mai trovato una risposta definitiva da nessuna parte se questa è davvero la cosa giusta da fare o se lo fa veramente. Speravo che qualcuno che sa per certo avrebbe peso su quello. –

+0

Ho aggiunto una finestra di messaggio nell'evento Disposed di BackgroundWorker e la finestra di messaggio visualizzata dopo il completamento dell'operazione asincrona. Per me, sembra che BackgroundWorker si stacchi da solo dopo che ha completato il suo lavoro. Sei d'accordo con quella valutazione? – joek1975

3

Chiama il tuo evento RunWorkerCompleted.

BackgroundWorker wkr = new BackgroundWorker(); 
wkr.DoWork += (s, e) => { 
    // Do long running task. 
}; 
wkr.RunWorkerCompleted += (s, e) => { 
    try { 
     if (e.Error != null) { 
      // Handle failure. 
     } 
    } finally { 
     // Use wkr outer instead of casting. 
     wkr.Dispose(); 
    } 
}; 
wkr.RunWorkerAsync(); 

L'extra try/finally è quello di garantire che Dispose viene chiamato se il codice di completamento solleva un'eccezione.

0

Il gestore di completamento viene eseguito sul thread originale (vale a dire, non il thread in background dal pool di thread)! I risultati del test in realtà confermano questa premessa.

Problemi correlati