2008-09-23 7 views
110

Betrachten wir ein hypothetische Methode eines Objekts, das Material für Sie tut:Wie warten, bis ein BackgroundWorker abbricht?

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

    ... 

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

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

Wie kann man für einen Background warten zu tun?


In der Vergangenheit Menschen haben versucht:

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

Aber this deadlocks, weil IsBusy erst gelöscht, nachdem das RunWorkerCompleted Ereignis behandelt wird, und das Ereignis nicht behandelt bekommen kann, bis die Anwendung im Leerlauf geht . Die Anwendung wird nicht inaktiv, bis der Worker fertig ist. (Außerdem ist es eine geschäftige Schleife - ekelhaft.)

andere vorgeschlagen haben add kludging es in:

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

Das Problem mit diesem ist, dass es Application.DoEvents() Nachrichten verursacht zur Zeit in der Warteschlange verarbeitet werden, die Ursache Re-entrancy-Probleme (.NET ist nicht einspringend).

Ich würde hoffen, dass irgendeine Lösung beteiligt Ereignissynchronisationsobjekte zu verwenden, wobei der Code wartet für eine Veranstaltung -, dass der RunWorkerCompleted Event-Handler-Sets des Arbeitnehmers. Etwas wie:

Event _workerDoneEvent = new WaitHandle(); 

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

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

Aber ich bin zurück in die Sackgasse: Der Event-Handler kann nicht ausgeführt werden, bis die Anwendung im Leerlauf geht, und die Anwendung wird nicht untätig gehen, weil es für ein Ereignis wartet.

Also, wie können Sie warten, bis ein BackgroundWorker fertig ist?


aktualisieren scheinen Menschen mit dieser Frage verwechselt werden. Sie scheinen zu glauben, dass ich als den Background verwenden werden:

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

Das heißt nicht es, das ist nicht, was ich tue, und das ist nicht was hier gefragt wird. Wenn dies der Fall wäre, hätte es keinen Sinn, einen Hintergrundarbeiter zu verwenden.

Antwort

116

Wenn ich Ihre Anforderung richtig verstehen, könnte man so etwas tun (Code nicht getestet, sondern zeigt die allgemeine Idee):

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

Das funktioniert gut, bis auf einen Fehler. Das Ereignis muss im Status "not set" erstellt werden: private AutoResetEvent _resetEvent = new AutoResetEvent (false); –

+1

Also, wenn Sie Ihren Beitrag bearbeiten und dieses Update machen könnten, wird es eine Antwort für das Alter sein! –

+0

Guter Fang; behoben :) –

4

Sie können prüfen, in die RunWorkerCompletedEventArgs im RunWorkerCompletedEventHandler zu sehen, was der Status war. Erfolg, abgebrochen oder ein Fehler.

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

aktualisieren: Um zu sehen, ob Ihre Arbeiter .CancelAsync() aufgerufen hat dies durch den Einsatz:

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

Die Anforderung ist, dass CancelDoingStuff() nicht zurückgeben kann, bis der Worker abgeschlossen ist. Es ist nicht von Interesse zu sehen, wie es wirklich fertig ist. –

+0

Dann müssen Sie ein Ereignis erstellen. Es hat wirklich nichts mit BackgroundWorker zu tun, Sie müssen nur ein Ereignis implementieren, darauf achten und feuern, wenn Sie fertig sind. Und RunWorkerCompletedEventHandler ist, wenn es fertig ist. Feuer ein anderes Ereignis. –

3

Warum nicht, können Sie die BackgroundWorker.RunWorkerCompleted Ereignis binden in gerade. Es ist ein Rückruf, der auftritt, wenn die Hintergrundoperation abgeschlossen wurde, abgebrochen wurde oder eine Ausnahme ausgelöst hat."

+0

Da die Person, die das DoesStuff-Objekt verwendet, sie zum Abbrechen aufgefordert hat. Gemeinsame Ressourcen, die das Objekt verwendet, werden gerade getrennt, entfernt, entsorgt, geschlossen, und es muss wissen, dass es getan ist, damit ich weitermachen kann. –

1

Ich verstehe nicht, warum Sie sich für einen Background warten wollen würden, um zu vollenden;. Es scheint wirklich, wie das genaue Gegenteil der Motivation für die Klasse

Sie konnten jedoch jede Methode mit einem Start rufen zu worker.IsBusy und sie beendet, wenn es läuft.

+0

Schlechte Wortwahl; Ich warte nicht darauf, dass es abgeschlossen wird. –

0

Hm vielleicht bin ich nicht immer Ihre Frage richtig.

die Background ruft die WorkerCompleted Ereignis einmal seine ‚workermethod‘ (die Methode/Funktion/sub dass Griffe der backgroundworker.doWork-event) beendet ist, so gibt es keine Notwendigkeit, zu überprüfen, ob die BW läuft noch. Wenn Sie Ihre Arbeiter stoppen wollen überprüfen Sie die cancellation pending property in Ihrem ‚Arbeitnehmer-Methode‘.

+0

Ich verstehe, dass der Arbeiter die CancellationPending-Eigenschaft überwachen und so bald wie möglich beenden muss. Aber wie wartet die Person auf der Außenseite, die den Hintergrundarbeiter aufgefordert hat abzubrechen, darauf warten, dass es getan wird? –

+0

WorkCompleted-Ereignis wird weiterhin ausgelöst. –

+0

"Aber wie wartet die Person, die den Hintergrundarbeiter aufgefordert hat abzubrechen, darauf, dass es gemacht wird?" –

4

Sie nicht Wartezeit für den Arbeiter Hintergrund tun vervollständigen zu. Das vereitelt ziemlich genau den Zweck, einen separaten Thread zu starten. Stattdessen sollten Sie Ihre Methode beenden, lassen Sie und beliebigen Code verschieben, die nach Abschluss zu einem anderen Ort abhängt. Sie lassen sich von der Arbeitskraft sagen, wann es fertig ist, und dann den restlichen Code aufrufen.

Wenn Sie möchten warten Sie auf für etwas zu vervollständigen verwenden Sie ein anderes Threading-Konstrukt, das eine WaitHandle bereitstellt.

+1

Können Sie einen vorschlagen? Ein Hintergrund-Worker scheint das einzige Threading-Konstrukt zu sein, das Benachrichtigungen an den Thread senden kann, der das BackgroundWorker-Objekt erstellt hat. –

+0

Der Hintergrundarbeiter ist ein _UI_ Konstrukt. Es verwendet nur Ereignisse, die Ihr eigener Code noch benötigt, um zu wissen, wie man anruft. Nichts hält Sie davon ab, eigene Delegierte dafür zu schaffen. –

0

Der Workflow eines BackgroundWorker Objekt, das Sie im Grunde erfordert das RunWorkerCompleted Ereignis behandeln sowohl für die normale Ausführung als auch für Anwendungsunterbrechungen. Aus diesem Grund ist die Eigenschaft RunWorkerCompletedEventArgs.Cancelled existiert. Grundsätzlich erfordert dies richtig tun, dass Sie als Ihre Abbrechen Methode ein asynchrones Verfahren an sich sein.

Hier ist ein Beispiel:

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; 
     } 
    } 
} 

Wenn Sie wirklich wirklich Sie Ihre Methode nicht verlassen wollen, würde ich vorschlagen eine Flagge wie ein AutoResetEvent auf einem BackgroundWorker abgeleitet setzen, dann OnRunWorkerCompleted außer Kraft zu setzen die Flagge. Es ist immer noch irgendwie kludgy; Ich würde empfehlen, das Abbruch-Ereignis wie eine asynchrone Methode zu behandeln und zu tun, was es gerade tut, in der RunWorkerCompleted Handler.

+0

Verschieben von Code zu RunWorkerCompleted ist nicht dort, wo es gehört, und ist nicht schön. –

13

Es gibt ein Problem mit this Antwort. Die Benutzeroberfläche muss weiterhin Nachrichten verarbeiten, während Sie warten, andernfalls wird sie nicht neu gezeichnet. Dies ist ein Problem, wenn Ihr Hintergrundmitarbeiter lange braucht, um auf die Abbruchanforderung zu reagieren. Ein zweiter Fehler ist, dass niemals aufgerufen wird, wenn der Worker-Thread eine Ausnahme auslöst - der Haupt-Thread wartet auf unbestimmte Zeit - jedoch könnte dieser Fehler leicht mit einem try/finally-Block behoben werden.

Eine Möglichkeit, dies zu tun, besteht darin, einen modalen Dialog anzuzeigen, der einen Timer hat, der wiederholt überprüft, ob der Hintergrundarbeiter die Arbeit beendet hat (oder in Ihrem Fall beendet hat). Sobald der Hintergrundarbeiter fertig ist, gibt der modale Dialog die Kontrolle an Ihre Anwendung zurück. Der Benutzer kann nicht mit der Benutzeroberfläche interagieren, bis dies geschieht.Eine andere Methode (vorausgesetzt, Sie haben maximal ein offenes modales Fenster geöffnet) besteht darin, ActiveForm.Enabled = false zu setzen und anschließend Application, DoEvents zu wiederholen, bis der Hintergrund-Worker die Beendigung beendet hat. Anschließend können Sie ActiveForm.Enabled = festlegen wieder wahr.

+4

Das mag ein Problem sein, aber es wird grundsätzlich als Teil der Frage akzeptiert "Wie ** warten **, bis ein BackgroundWorker abbricht". Warten bedeutet, dass du wartest, du tust nichts anderes. Dies beinhaltet auch die Verarbeitung von Nachrichten. Wenn ich nicht auf den Hintergrundarbeiter warten wollte, konnte ich einfach .CancelAsync aufrufen. Aber das ist nicht die Design-Anforderung hier. –

+1

+1, um den Wert der CancelAsync-Methode anzugeben, Verse * waiting * für den Hintergrund-Worker. –

10

Fast alle von Ihnen sind durch die Frage verwirrt und verstehen nicht, wie ein Arbeiter benutzt wird.

Betrachten wir ein RunWorkerComplete Ereignishandler:

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; 
} 

Und alles ist gut.

Jetzt kommt eine Situation, in der der Anrufer den Countdown abbrechen muss, weil er eine Notfall-Selbstzerstörung der Rakete durchführen muss.

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

    StartClaxon(); 
    SelfDestruct(); 
} 

Und es gibt auch eine Situation, wo wir brauchen, um die Rakete die Zugangstüren zu öffnen, aber nicht während eines Countdown zu tun:

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

    if (!rocketOnPad) 
     DisengageAllGateLatches(); 
} 

Und schließlich müssen wir die Rakete de-Kraftstoff , aber das ist nicht während eines Countdowns erlaubt:

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

    if (rocketOnPad) 
     OpenFuelValves(); 
} 

Ohne die Fähigkeit, für einen Arbeiter zu warten, um zu kündigen, müssen wir alle drei Methoden zum RunWorkerCompletedEvent bewegen:

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(); 
} 

Jetzt könnte ich meinen Code so schreiben, aber ich werde einfach nicht. Es ist mir egal, ich bin einfach nicht.

+11

Wo ist WaitForWorkerToFinish Methode? ein vollständiger Quellcode? – Kiquenet

-2

oh man, einige von diesen sind lächerlich komplex geworden. Sie müssen lediglich die BackgroundWorker.CancellationPending-Eigenschaft im DoWork-Handler überprüfen. Sie können es jederzeit überprüfen. Sobald es aussteht, setzen Sie e.Cancel = True und brechen Sie von der Methode ab.

// Methode hier private void Worker_DoWork (object sender, DoWorkEventArgs e) { Background bw = (Sender als Background);

// do stuff 

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

// do other stuff 

}

+1

Und mit dieser Lösung wie ist die Person, die CancelAsync aufgerufen hat, gezwungen, auf den Hintergrundarbeiter zu warten, um abzubrechen? –

0

Ich bin ein wenig spät, um die Partei hier (ca. 4 Jahre), aber was ist einen asynchronen Thread Einrichtung, die ein Besetzt Schleife ohne Sperren den UI umgehen kann, habe dann den Rückruf aus diesem Thread die Bestätigung, dass der BackgroundWorker die Löschung beendet hat?

Etwas wie folgt aus:

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 
    } 
} 

Im Wesentlichen, was das tut, ist ein anderer Thread abfeuern im Hintergrund laufen zu lassen, die gerade wartet darin beschäftigt Schleife zu sehen ist, wenn die MyWorker abgeschlossen hat. Sobald MyWorker beendet ist, wird der Thread beendet und wir können AsyncCallback verwenden, um die Methode auszuführen, die wir brauchen, um der erfolgreichen Löschung zu folgen - es wird wie ein Pseudo-Ereignis funktionieren. Da dies vom UI-Thread getrennt ist, wird die UI nicht gesperrt, während wir auf MyWorker warten, bis die Löschung abgeschlossen ist. Wenn Ihre Absicht wirklich ist, zu sperren und auf den Abbruch zu warten, dann ist das für Sie nutzlos, aber wenn Sie nur warten wollen, damit Sie einen anderen Prozess starten können, dann funktioniert das gut.

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

Was ist Ihre Frage? In SO müssen Sie spezifisch sein, wenn Sie Fragen stellen –

+0

@This ist Antwort keine Frage. –

0

Ich weiß das wirklich zu spät ist (5 Jahre), aber was Sie suchen, ist ein Thema und ein SynchronizationContext zu verwenden. Sie müssen UI-Aufrufe zurück zum Benutzeroberflächenthread "von Hand" marsieren, anstatt das Framework automatisch magisch ausführen zu lassen.

Damit können Sie einen Thread verwenden, auf den Sie gegebenenfalls warten können.

0

Fredrik Kalseths Lösung für dieses Problem ist das Beste, was ich bisher gefunden habe. Andere Lösungen verwenden Application.DoEvent(), die Probleme verursachen oder einfach nicht funktionieren. Lassen Sie mich seine Lösung in eine wiederverwendbare Klasse umwandeln. Da BackgroundWorker nicht abgedichtet ist, können wir unsere Klasse daraus ziehen:

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(); 
     } 
    } 
} 

Mit Fahnen und Verblocken, stellen wir sicher, dass _resetEvent.WaitOne() wirklich nur dann, wenn einige Arbeiten begonnen wurde, sonst wird aufgerufen _resetEvent.Set(); vielleicht nie genannt worden!

Das try-finally stellt sicher, dass _resetEvent.Set(); aufgerufen wird, auch wenn eine Ausnahme in unserem DoWork-Handler auftreten sollte. Sonst könnte die Anwendung beim Aufruf CancelSync für immer einfrieren!

Wir würden es wie folgt verwenden:

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() + "%"; 
} 

Sie können auch einen Handler zum RunWorkerCompleted Ereignis hinzufügen, wie hier gezeigt:
          BackgroundWorker Class(Microsoft-Dokumentation).

1

Ich will nur sagen, dass ich hierher gekommen, weil ich einen Hintergrund Arbeiter müssen warten, während ich einen asynchronen Prozess ausgeführt wurde, während in einer Schleife, mein fix Weise einfacher, als alle diese anderen Sachen ^^

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

Nur Ich dachte mir, ich würde teilen, weil ich dort auf der Suche nach einer Lösung gelandet bin. Außerdem ist dies mein erster Post auf Stack Overflow, also wenn es schlecht ist oder irgendetwas, würde ich Kritiker lieben! :)

0

Schließen des Formulars schließt meine offene Protokolldatei. Mein Hintergrundarbeiter schreibt diese Logdatei, so dass ich MainWin_FormClosing() nicht beenden kann, bis mein Hintergrundarbeiter beendet wird. Wenn ich nicht darauf warte, dass mein Hintergrundarbeiter beendet wird, passieren Ausnahmen.

Warum ist das so schwer?

Eine einfache Thread.Sleep(1500) funktioniert, aber es verzögert das Herunterfahren (wenn zu lange) oder verursacht Ausnahmen (wenn zu kurz).

Um direkt nach dem Beenden des Hintergrundarbeiters herunterzufahren, verwenden Sie einfach eine Variable. Das funktioniert für mich:

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

Sie können aus dem RunWorkerCompleted-Ereignis Piggyback. Selbst wenn Sie bereits einen Event-Handler für _worker hinzugefügt haben, können Sie einen weiteren hinzufügen und sie werden in der Reihenfolge ausgeführt, in der sie hinzugefügt wurden.

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(); 
    } 
} 

könnte dies nützlich sein, wenn Sie mehrere Gründe, warum ein auftreten aufheben kann, wollen die Logik eines einzigen RunWorkerCompleted Handler komplizierter, als Sie machen. Beispielsweise Abbrechen, wenn ein Benutzer versucht, das Formular zu schließen:

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