Come attendere la cancellazione di un BackgroundWorker?


110

consideri un ipotetico metodo di un oggetto che fa cose per voi:

public class DoesStuff 
{ 
    BackgroundWorker _worker = new BackgroundWorker(); 

    ... 

    public void CancelDoingStuff() 
    { 
     _worker.CancelAsync(); 

     //todo: Figure out a way to wait for BackgroundWorker to be cancelled. 
    } 
} 

Come si può attendere un BackgroundWorker da fare?


In passato la gente hanno provato:

while (_worker.IsBusy) 
{ 
    Sleep(100); 
} 

Ma this deadlocks, perché IsBusy non viene cancellato fino a dopo l'evento RunWorkerCompleted viene gestito, e che comunque non può ottenere trattati fino a quando l'applicazione diventa inattivo . L'applicazione non andrà inattiva fino a quando il lavoratore non ha finito. (Inoltre, è un ciclo occupato - disgustoso.)

Altri hanno aggiungono suggerito kludging in:

while (_worker.IsBusy) 
{ 
    Application.DoEvents(); 
} 

Il problema di questo è che è Application.DoEvents() provoca messaggi attualmente in coda da elaborare, che causa problemi di re-entrancy (.NET non è ri-entrante).

spero di usare qualche soluzione che coinvolgono oggetti di sincronizzazione per eventi, dove il codice attese per un evento - che RunWorkerCompleted gestori di eventi imposta del lavoratore. Qualcosa di simile:

Event _workerDoneEvent = new WaitHandle(); 

public void CancelDoingStuff() 
{ 
    _worker.CancelAsync(); 
    _workerDoneEvent.WaitOne(); 
} 

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e) 
{ 
    _workerDoneEvent.SetEvent(); 
} 

Ma io sono di nuovo alla situazione di stallo: il gestore di eventi non può essere eseguito fino a quando l'applicazione diventa inattivo, e l'applicazione non andrò al minimo perché è in attesa di un evento.

Quindi, come puoi aspettare che un Backgroundworker finisca?


Aggiornamento La gente sembra essere confuso da questa domanda. Sembrano pensare che userò la BackgroundWorker come:

BackgroundWorker worker = new BackgroundWorker(); 
worker.DoWork += MyWork; 
worker.RunWorkerAsync(); 
WaitForWorkerToFinish(worker); 

Questo non è esso, che è non quello che sto facendo, e che non è ciò che viene chiesto qui. Se così fosse, non avrebbe senso usare un lavoratore in background.

116

Se ho ben capito il vostro diritto requisito, si potrebbe fare qualcosa di simile (codice non testato, ma mostra l'idea generale):

private BackgroundWorker worker = new BackgroundWorker(); 
private AutoResetEvent _resetEvent = new AutoResetEvent(false); 

public Form1() 
{ 
    InitializeComponent(); 

    worker.DoWork += worker_DoWork; 
} 

public void Cancel() 
{ 
    worker.CancelAsync(); 
    _resetEvent.WaitOne(); // will block until _resetEvent.Set() call made 
} 

void worker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    while(!e.Cancel) 
    { 
     // do something 
    } 

    _resetEvent.Set(); // signal that worker is done 
} 
  0

Funziona alla grande, tranne un bug. L'evento deve essere creato nello stato "non impostato": private AutoResetEvent _resetEvent = new AutoResetEvent (false); 23 set. 082008-09-23 21:17:12

+1

Quindi, se potessi modificare il tuo post e fare quella correzione, sarà una risposta adatta per le età! 23 set. 082008-09-23 21:17:50

  0

Buona cattura; risolto :) 24 set. 082008-09-24 07:59:50

+7

Ciò bloccherà l'interfaccia utente (ad esempio, non ridistribuisce) se l'operatore in background impiega molto tempo per annullare. È meglio utilizzare uno dei seguenti metodi per interrompere l'interazione con l'interfaccia utente: http://stackoverflow.com/questions/123661/net-how-to-wait-for-a-backgroundworker-to-completo#126570 24 set. 082008-09-24 11:26:09

+1

+1 solo cosa ha ordinato il dottore ... anche se sono d'accordo con @Joe se la richiesta di cancellazione può richiedere più di un secondo. 30 lug. 092009-07-30 16:32:45

  0

Cosa succede quando CancelAsync viene gestito prima di WaitOne? Oppure è il pulsante Annulla funziona solo una volta. 01 feb. 112011-02-01 13:19:35

+3

Avevo bisogno di controllare il mittente '((BackgroundWorker)) .CancellationPending' per ottenere l'evento cancel 06 mar. 132013-03-06 08:12:56

  0

Questo non dovrebbe essere un evento ManualResetEvent? Cliccando due volte su "x" si potrebbe bloccare l'interfaccia utente 17 feb. 152015-02-17 14:41:29

  0

Come menzionato da Luuk, non è la proprietà Cancel che deve essere controllata, ma CancellationPending. In secondo luogo, come menziona Joel Coehoorn, in attesa che il thread finisca le sconfitte, lo scopo di usare un thread in primo luogo. 06 ott. 172017-10-06 09:17:12


4

È possibile controllare nelle RunWorkerCompletedEventArgs nel RunWorkerCompletedEventHandler di vedere ciò che lo stato è stato. Successo, annullato o errore.

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e) 
{ 
    if(e.Cancelled) 
    { 
     Console.WriteLine("The worker was cancelled."); 
    } 
} 

Aggiornamento: per vedere se il lavoratore ha chiamato .CancelAsync() utilizzando questo:

if (_worker.CancellationPending) 
{ 
    Console.WriteLine("Cancellation is pending, no need to call CancelAsync again"); 
} 
+1

Il requisito è che CancelDoingStuff() non possa essere restituito fino al completamento del lavoro. Il controllo di come è stato completato davvero non è interessante. 23 set. 082008-09-23 20:38:32

  0

Quindi dovrai creare un evento. In realtà non ha nulla a che fare con BackgroundWorker, devi solo implementare un evento, ascoltarlo e sparare quando hai finito. E RunWorkerCompletedEventHandler è quando ha finito. Fai fuoco a un altro evento. 23 set. 082008-09-23 21:03:01


3

Perché non puoi semplicemente legare nella BackgroundWorker.RunWorkerCompleted evento. È un callback che "si verifica quando l'operazione in background è stata completata, è stata annullata o ha generato un'eccezione."

  0

Poiché la persona che sta utilizzando l'oggetto DoesStuff ha chiesto di annullare. Le risorse condivise utilizzate dall'oggetto stanno per essere disconnesse, rimosse, eliminate, chiuse e devono sapere che è stato fatto in modo da poter andare avanti. 23 set. 082008-09-23 20:47:55


1

Non capisco perché ci si vuole aspettare un BackgroundWorker per completare;. Sembra davvero come l'esatto opposto della motivazione per la classe

Tuttavia, si potrebbe iniziare ogni metodo con una chiamare per worker.IsBusy e li hanno di uscita se è in esecuzione.

  0

Cattiva scelta di parole; non sto aspettando che si completi. 24 set. 082008-09-24 13:44:58


0

Hm forse io non sto ottenendo la tua domanda giusta.

il BackgroundWorker chiama l'evento WorkerCompleted una volta la sua 'workermethod' (il metodo/funzione/sub che gestisce lo backgroundworker.doWork-event) è finito quindi non è necessario verificare se il BW è ancora in esecuzione. Se si desidera impedire al lavoratore di controllare lo cancellation pending property all'interno del proprio "metodo di lavoro".

  0

capisco che il lavoratore deve monitorare la proprietà CancellationPending ed uscire il prima possibile. Ma come fa la persona all'esterno, chi ha richiesto l'addetto al background annulla, aspetta che sia fatto? 23 set. 082008-09-23 20:52:44

  0

L'evento WorkCompleted continuerà a essere attivato. 23 set. 082008-09-23 20:58:26

  0

"Ma come fa la persona all'esterno, chi ha richiesto l'addetto al background annulla, aspetta che sia fatto?" 24 set. 082008-09-24 13:45:55

  0

Si attende che venga eseguito attendendo l'attivazione dell'evento WorkCompleted. Se si desidera impedire all'utente di fare clic su tutta la GUI, è possibile utilizzare una delle soluzioni proposte da @Joe nella sua [risposta sopra] (http://stackoverflow.com/a/126570/55487) (disabilita il modulo o mostra qualcosa di modale). In altre parole, lascia che il ciclo di inattività del sistema ti stia aspettando; ti dirà quando è finito (sparando l'evento). 07 mar. 132013-03-07 17:07:31


4

non attendere il completamento dell'operatore in background. Ciò praticamente annulla lo scopo di avviare un thread separato. Invece, dovresti lasciare che il tuo metodo finisca e spostare qualsiasi codice che dipende dal completamento in un posto diverso. Lascia che il lavoratore ti dica quando ha finito e chiama il codice rimanente.

Se si desidera attendere per completare qualcosa utilizzare un diverso costrutto di threading che fornisce un WaitHandle.

+1

Puoi suggerirne uno? Un worker in background sembra essere l'unico costrutto di threading in grado di inviare notifiche al thread che ha creato l'oggetto BackgroundWorker. 23 set. 082008-09-23 21:01:10

  0

Backgroundworker è un costrutto _UI_. Utilizza solo eventi che il tuo codice ha ancora bisogno di sapere come chiamare. Niente ti impedisce di creare i tuoi delegati per questo. 23 set. 082008-09-23 22:00:27


0

Il flusso di lavoro di un oggetto BackgroundWorker si richiede fondamentalmente per gestire l'evento RunWorkerCompleted sia per casi di utilizzo normale che per cancellazione da parte dell'utente. Questo è il motivo per cui la proprietà RunWorkerCompletedEventArgs.Cancelled esiste. Fondamentalmente, fare ciò correttamente richiede che consideri il tuo metodo Cancel come un metodo asincrono di per sé.

Ecco un esempio:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Windows.Forms; 
using System.ComponentModel; 

namespace WindowsFormsApplication1 
{ 
    public class AsyncForm : Form 
    { 
     private Button _startButton; 
     private Label _statusLabel; 
     private Button _stopButton; 
     private MyWorker _worker; 

     public AsyncForm() 
     { 
      var layoutPanel = new TableLayoutPanel(); 
      layoutPanel.Dock = DockStyle.Fill; 
      layoutPanel.ColumnStyles.Add(new ColumnStyle()); 
      layoutPanel.ColumnStyles.Add(new ColumnStyle()); 
      layoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); 
      layoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); 

      _statusLabel = new Label(); 
      _statusLabel.Text = "Idle."; 
      layoutPanel.Controls.Add(_statusLabel, 0, 0); 

      _startButton = new Button(); 
      _startButton.Text = "Start"; 
      _startButton.Click += HandleStartButton; 
      layoutPanel.Controls.Add(_startButton, 0, 1); 

      _stopButton = new Button(); 
      _stopButton.Enabled = false; 
      _stopButton.Text = "Stop"; 
      _stopButton.Click += HandleStopButton; 
      layoutPanel.Controls.Add(_stopButton, 1, 1); 

      this.Controls.Add(layoutPanel); 
     } 

     private void HandleStartButton(object sender, EventArgs e) 
     { 
      _stopButton.Enabled = true; 
      _startButton.Enabled = false; 

      _worker = new MyWorker() { WorkerSupportsCancellation = true }; 
      _worker.RunWorkerCompleted += HandleWorkerCompleted; 
      _worker.RunWorkerAsync(); 

      _statusLabel.Text = "Running..."; 
     } 

     private void HandleStopButton(object sender, EventArgs e) 
     { 
      _worker.CancelAsync(); 
      _statusLabel.Text = "Cancelling..."; 
     } 

     private void HandleWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      if (e.Cancelled) 
      { 
       _statusLabel.Text = "Cancelled!"; 
      } 
      else 
      { 
       _statusLabel.Text = "Completed."; 
      } 

      _stopButton.Enabled = false; 
      _startButton.Enabled = true; 
     } 

    } 

    public class MyWorker : BackgroundWorker 
    { 
     protected override void OnDoWork(DoWorkEventArgs e) 
     { 
      base.OnDoWork(e); 

      for (int i = 0; i < 10; i++) 
      { 
       System.Threading.Thread.Sleep(500); 

       if (this.CancellationPending) 
       { 
        e.Cancel = true; 
        e.Result = false; 
        return; 
       } 
      } 

      e.Result = true; 
     } 
    } 
} 

Se davvero non si vuole il metodo per uscire, io suggerirei di mettere una bandiera come un AutoResetEvent su un derivato BackgroundWorker, quindi sovrascrivere OnRunWorkerCompleted per impostare la bandiera. Comunque è ancora piuttosto crudele; Consiglierei di considerare l'evento cancel come un metodo asincrono e di fare qualsiasi cosa stia facendo nel gestore RunWorkerCompleted.

  0

Lo spostamento del codice in RunWorkerCompleted non è il punto a cui appartiene e non è bello. 24 set. 082008-09-24 13:47:00


13

C'è un problema con la risposta this. L'interfaccia utente deve continuare a elaborare i messaggi mentre si è in attesa, altrimenti non verrà ridisegnata, il che sarà un problema se il proprio operatore in background impiegherà molto tempo per rispondere alla richiesta di annullamento.

Un secondo difetto è che _resetEvent.Set() non verrà mai chiamato se il thread di lavoro genera un'eccezione, lasciando il thread principale in attesa indefinitamente, tuttavia questa vulnerabilità potrebbe essere facilmente risolta con un blocco try/finally.

Un modo per fare ciò è visualizzare una finestra di dialogo modale che ha un timer che controlla ripetutamente se il lavoratore in background ha terminato il lavoro (o ha terminato di cancellare nel tuo caso). Una volta che l'operatore in background ha terminato, la finestra di dialogo modale restituisce il controllo all'applicazione. L'utente non può interagire con l'interfaccia utente fino a quando ciò non si verifica.

Un altro metodo (supponendo che sia aperta una finestra non modale) è impostare ActiveForm.Enabled = false, quindi eseguire il loop su Application, DoEvents fino a quando l'operatore in background ha terminato l'annullamento, dopo di che è possibile impostare ActiveForm.Enabled = vero di nuovo.

+4

Questo potrebbe essere un problema, ma è fondamentalmente accettato come parte della domanda, "Come ** aspettare ** che un BackgroundWorker annulli". Aspettare significa aspettare, non fai altro. Ciò include anche l'elaborazione dei messaggi. Se non volessi aspettare il lavoratore in background, potresti semplicemente chiamare .CancelAsync. Ma questo non è il requisito di progettazione qui. 11 lug. 092009-07-11 14:07:52

+1

+1 per indicare il valore del metodo CancelAsync, i versi * in attesa * per l'operatore in background. 11 lug. 092009-07-11 14:10:04


10

Quasi tutti sono confusi dalla domanda e non capiscono come viene utilizzato un lavoratore.

consideri un gestore di eventi RunWorkerComplete:

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    if (!e.Cancelled) 
    { 
     rocketOnPad = false; 
     label1.Text = "Rocket launch complete."; 
    } 
    else 
    { 
     rocketOnPad = true; 
     label1.Text = "Rocket launch aborted."; 
    } 
    worker = null; 
} 

E tutto è buono.

Ora arriva una situazione in cui il chiamante deve interrompere il conto alla rovescia perché devono eseguire un'autodistruzione di emergenza del razzo.

private void BlowUpRocket() 
{ 
    if (worker != null) 
    { 
     worker.CancelAsync(); 
     WaitForWorkerToFinish(worker); 
     worker = null; 
    } 

    StartClaxon(); 
    SelfDestruct(); 
} 

E c'è anche una situazione in cui abbiamo bisogno di aprire le porte di accesso al razzo, ma non mentre si fa un conto alla rovescia:

private void OpenAccessGates() 
{ 
    if (worker != null) 
    { 
     worker.CancelAsync(); 
     WaitForWorkerToFinish(worker); 
     worker = null; 
    } 

    if (!rocketOnPad) 
     DisengageAllGateLatches(); 
} 

E, infine, abbiamo bisogno di de-alimentare il razzo , ma che non è permesso durante un conto alla rovescia:

private void DrainRocket() 
{ 
    if (worker != null) 
    { 
     worker.CancelAsync(); 
     WaitForWorkerToFinish(worker); 
     worker = null; 
    } 

    if (rocketOnPad) 
     OpenFuelValves(); 
} 

senza la capacità di aspettare per un lavoratore di annullare, dobbiamo muoverci tutti e tre i metodi per la RunWorkerCompletedEvent:

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    if (!e.Cancelled) 
    { 
     rocketOnPad = false; 
     label1.Text = "Rocket launch complete."; 
    } 
    else 
    { 
     rocketOnPad = true; 
     label1.Text = "Rocket launch aborted."; 
    } 
    worker = null; 

    if (delayedBlowUpRocket) 
     BlowUpRocket(); 
    else if (delayedOpenAccessGates) 
     OpenAccessGates(); 
    else if (delayedDrainRocket) 
     DrainRocket(); 
} 

private void BlowUpRocket() 
{ 
    if (worker != null) 
    { 
     delayedBlowUpRocket = true; 
     worker.CancelAsync(); 
     return; 
    } 

    StartClaxon(); 
    SelfDestruct(); 
} 

private void OpenAccessGates() 
{ 
    if (worker != null) 
    { 
     delayedOpenAccessGates = true; 
     worker.CancelAsync(); 
     return; 
    } 

    if (!rocketOnPad) 
     DisengageAllGateLatches(); 
} 

private void DrainRocket() 
{ 
    if (worker != null) 
    { 
     delayedDrainRocket = true; 
     worker.CancelAsync(); 
     return; 
    } 

    if (rocketOnPad) 
     OpenFuelValves(); 
} 

Ora potrei scrivere il mio codice in questo modo, ma non lo farò. Non mi interessa, io non sono.

+11

Dov'è il metodo WaitForWorkerToFinish? qualsiasi codice sorgente completo? 11 lug. 122012-07-11 11:29:32


-2

oh uomo, alcuni di questi sono diventati ridicolmente complessi. tutto ciò che devi fare è controllare la proprietà BackgroundWorker.CancellationPending all'interno del gestore di DoWork. puoi controllarlo in qualsiasi momento. una volta in sospeso, imposta e.Cancel = True e bail dal metodo.

// metodo qui Worker_DoWork private void (object sender, DoWorkEventArgs e) { BackgroundWorker bw = (mittente come BackgroundWorker);

// do stuff 

if(bw.CancellationPending) 
{ 
    e.Cancel = True; 
    return; 
} 

// do other stuff 

}

+1

E con questa soluzione, in che modo la persona che ha chiamato CancelAsync ha dovuto attendere l'annullamento dell'operatore in background? 06 ott. 082008-10-06 15:58:53


0

sono un po 'in ritardo alla festa qui (circa 4 anni) ma per quanto riguarda la creazione di un thread asincrono in grado di gestire un ciclo occupato senza bloccare l'interfaccia utente, quindi avere il callback da quel thread è la conferma che BackgroundWorker ha finito di cancellare?

Qualcosa di simile a questo:

class Test : Form 
{ 
    private BackgroundWorker MyWorker = new BackgroundWorker(); 

    public Test() { 
     MyWorker.DoWork += new DoWorkEventHandler(MyWorker_DoWork); 
    } 

    void MyWorker_DoWork(object sender, DoWorkEventArgs e) { 
     for (int i = 0; i < 100; i++) { 
      //Do stuff here 
      System.Threading.Thread.Sleep((new Random()).Next(0, 1000)); //WARN: Artificial latency here 
      if (MyWorker.CancellationPending) { return; } //Bail out if MyWorker is cancelled 
     } 
    } 

    public void CancelWorker() { 
     if (MyWorker != null && MyWorker.IsBusy) { 
      MyWorker.CancelAsync(); 
      System.Threading.ThreadStart WaitThread = new System.Threading.ThreadStart(delegate() { 
       while (MyWorker.IsBusy) { 
        System.Threading.Thread.Sleep(100); 
       } 
      }); 
      WaitThread.BeginInvoke(a => { 
       Invoke((MethodInvoker)delegate() { //Invoke your StuffAfterCancellation call back onto the UI thread 
        StuffAfterCancellation(); 
       }); 
      }, null); 
     } else { 
      StuffAfterCancellation(); 
     } 
    } 

    private void StuffAfterCancellation() { 
     //Things to do after MyWorker is cancelled 
    } 
} 

In sostanza ciò che fa è sparare un altro thread per l'esecuzione in background che appena attende nel suo ciclo occupato per vedere se il MyWorker ha completato. Una volta che MyWorker ha terminato di cancellare, il thread uscirà e possiamo usare il suo AsyncCallback per eseguire qualsiasi metodo necessario per seguire l'annullamento riuscito - funzionerà come un evento psuedo. Poiché questo è separato dal thread dell'interfaccia utente, non bloccherà l'interfaccia utente mentre aspettiamo che MyWorker termini l'annullamento. Se la tua intenzione è di bloccare e aspettare l'annullamento, questo è inutile per te, ma se vuoi solo aspettare in modo da poter avviare un altro processo, allora funziona bene.


0
Imports System.Net 
Imports System.IO 
Imports System.Text 

Public Class Form1 
    Dim f As New Windows.Forms.Form 
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 
    BackgroundWorker1.WorkerReportsProgress = True 
    BackgroundWorker1.RunWorkerAsync() 
    Dim l As New Label 
    l.Text = "Please Wait" 
    f.Controls.Add(l) 
    l.Dock = DockStyle.Fill 
    f.StartPosition = FormStartPosition.CenterScreen 
    f.FormBorderStyle = Windows.Forms.FormBorderStyle.None 
    While BackgroundWorker1.IsBusy 
     f.ShowDialog() 
    End While 
End Sub 




Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork 

    Dim i As Integer 
    For i = 1 To 5 
     Threading.Thread.Sleep(5000) 
     BackgroundWorker1.ReportProgress((i/5) * 100) 
    Next 
End Sub 

Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged 
    Me.Text = e.ProgressPercentage 

End Sub 

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted 

    f.Close() 

End Sub 

End Class 
  0

Qual è la tua domanda? In SO devi essere specifico quando fai domande 28 ago. 132013-08-28 10:38:24

  0

@Questa risposta non è una domanda. 28 ago. 132013-08-28 10:39:49


0

So che questo è davvero tardi (5 anni), ma quello che stai cercando è quello di utilizzare un filo e un SynchronizationContext. Dovrai eseguire il marshalling dell'interfaccia utente per richiamare il thread dell'interfaccia utente "manualmente" piuttosto che lasciare che Framework lo faccia automaticamente.

Ciò consente di utilizzare una discussione che è possibile attendere, se necessario.


0

La soluzione di Fredrik Kalseth a questo problema è la migliore che ho trovato finora. Altre soluzioni usano Application.DoEvent() che possono causare problemi o semplicemente non funzionano. Lascia che lanci la sua soluzione in una classe riutilizzabile. Dal momento che BackgroundWorker non è sigillato, possiamo derivare la nostra classe da essa:

public class BackgroundWorkerEx : BackgroundWorker 
{ 
    private AutoResetEvent _resetEvent = new AutoResetEvent(false); 
    private bool _resetting, _started; 
    private object _lockObject = new object(); 

    public void CancelSync() 
    { 
     bool doReset = false; 
     lock (_lockObject) { 
      if (_started && !_resetting) { 
       _resetting = true; 
       doReset = true; 
      } 
     } 
     if (doReset) { 
      CancelAsync(); 
      _resetEvent.WaitOne(); 
      lock (_lockObject) { 
       _started = false; 
       _resetting = false; 
      } 
     } 
    } 

    protected override void OnDoWork(DoWorkEventArgs e) 
    { 
     lock (_lockObject) { 
      _resetting = false; 
      _started = true; 
      _resetEvent.Reset(); 
     } 
     try { 
      base.OnDoWork(e); 
     } finally { 
      _resetEvent.Set(); 
     } 
    } 
} 

con bandiere e bloccaggio corretto, facciamo in modo che _resetEvent.WaitOne() in realtà viene chiamato solo se è stato avviato un lavoro, altrimenti _resetEvent.Set(); potrebbe mai stato chiamato!

Il try-finally assicura che venga chiamato _resetEvent.Set();, anche se dovesse verificarsi un'eccezione nel nostro gestore DoWork. In caso contrario, l'applicazione potrebbe bloccarsi per sempre quando si chiama CancelSync!

Vorremmo usare in questo modo:

BackgroundWorkerEx _worker; 

void StartWork() 
{ 
    StopWork(); 
    _worker = new BackgroundWorkerEx { 
     WorkerSupportsCancellation = true, 
     WorkerReportsProgress = true 
    }; 
    _worker.DoWork += Worker_DoWork; 
    _worker.ProgressChanged += Worker_ProgressChanged; 
} 

void StopWork() 
{ 
    if (_worker != null) { 
     _worker.CancelSync(); // Use our new method. 
    } 
} 

private void Worker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    for (int i = 1; i <= 20; i++) { 
     if (worker.CancellationPending) { 
      e.Cancel = true; 
      break; 
     } else { 
      // Simulate a time consuming operation. 
      System.Threading.Thread.Sleep(500); 
      worker.ReportProgress(5 * i); 
     } 
    } 
} 

private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
{ 
    progressLabel.Text = e.ProgressPercentage.ToString() + "%"; 
} 

È inoltre possibile aggiungere un gestore per l'evento RunWorkerCompleted come illustrato di seguito:
          BackgroundWorker Class(documentazione di Microsoft).


1

Voglio solo dire che sono venuto qui perché ho bisogno di un operaio sfondo di aspettare mentre stavo correndo un processo asincrono, mentre in un ciclo, il mio fix era il modo più facile di tutte queste altre cose ^^

foreach(DataRow rw in dt.Rows) 
{ 
    //loop code 
    while(!backgroundWorker1.IsBusy) 
    { 
     backgroundWorker1.RunWorkerAsync(); 
    } 
} 

Proprio Ho pensato che avrei condiviso perché questo è dove sono finito mentre cercavo una soluzione. Inoltre, questo è il mio primo post sullo stack overflow quindi se è male o altro mi piacerebbe i critici! :)


0

La chiusura del modulo chiude il mio file di registro aperto. Il mio assistente in background scrive quel file di log, quindi non posso permettere a MainWin_FormClosing() di finire finché il mio background worker non termina. Se non aspetto che il mio assistente in background termini, si verificano eccezioni.

Perché è così difficile?

Un semplice Thread.Sleep(1500) funziona, ma ritarda l'arresto (se troppo lungo) o causa eccezioni (se troppo breve).

Per arrestare subito dopo l'interruzione dell'attività di background, è sufficiente utilizzare una variabile. Questo funziona per me:

private volatile bool bwRunning = false; 

... 

private void MainWin_FormClosing(Object sender, FormClosingEventArgs e) 
{ 
    ... // Clean house as-needed. 

    bwInstance.CancelAsync(); // Flag background worker to stop. 
    while (bwRunning) 
     Thread.Sleep(100); // Wait for background worker to stop. 
} // (The form really gets closed now.) 

... 

private void bwBody(object sender, DoWorkEventArgs e) 
{ 
    bwRunning = true; 

    BackgroundWorker bw = sender as BackgroundWorker; 

    ... // Set up (open logfile, etc.) 

    for (; ;) // infinite loop 
    { 
     ... 
     if (bw.CancellationPending) break; 
     ... 
    } 

    ... // Tear down (close logfile, etc.) 

    bwRunning = false; 
} // (bwInstance dies now.) 

0

È possibile eseguire il backup dell'evento RunWorkerCompleted. Anche se hai già aggiunto un gestore di eventi per _worker, puoi aggiungerne un altro che verranno eseguiti nell'ordine in cui sono stati aggiunti.

public class DoesStuff 
{ 
    BackgroundWorker _worker = new BackgroundWorker(); 

    ... 

    public void CancelDoingStuff() 
    { 
     _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) => 
     { 
      // do whatever you want to do when the cancel completes in here! 
     }); 
     _worker.CancelAsync(); 
    } 
} 

questo potrebbe essere utile se si dispone di più motivi per cui un annullare può verificarsi, rendendo la logica di un singolo gestore RunWorkerCompleted più complicato di quanto si desidera. Ad esempio, annullamento quando un utente tenta di chiudere il modulo:

void Form1_FormClosing(object sender, FormClosingEventArgs e) 
{ 
    if (_worker != null) 
    { 
     _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender3, e3) => this.Close()); 
     _worker.CancelAsync(); 
     e.Cancel = true; 
    } 
}