Stellen Sie sich eine hypothetische Methode eines Objekts vor, das Dinge für Sie erledigt:
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 warten, bis ein BackgroundWorker fertig ist?
In der Vergangenheit haben die Leute versucht:
while (_worker.IsBusy)
{
Sleep(100);
}
Aber diese Deadlocks , da IsBusy
wird nicht gelöscht , bis das RunWorkerCompleted
Ereignis behandelt wird, und das Ereignis nicht behandelt werden , bis die Anwendung im Leerlauf geht. Die Anwendung wird erst dann inaktiv, wenn der Worker fertig ist. (Außerdem ist es eine belebte Schleife - widerlich.)
Andere haben vorgeschlagen, es zu kludgen in:
while (_worker.IsBusy)
{
Application.DoEvents();
}
Das Problem dabei ist, Application.DoEvents()
dass Nachrichten, die sich derzeit in der Warteschlange befinden, verarbeitet werden, was zu Problemen beim erneuten Eintritt führt (.NET wird nicht erneut eingegeben).
Ich würde hoffen, eine Lösung mit Ereignissynchronisationsobjekten zu verwenden, bei der der Code auf ein Ereignis wartet - das die RunWorkerCompleted
Ereignishandler des Arbeiters festlegen. 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 zum Deadlock: Der Ereignishandler kann erst ausgeführt werden, wenn die Anwendung inaktiv ist, und die Anwendung wird nicht inaktiv, weil sie auf ein Ereignis wartet.
Wie können Sie also warten, bis ein BackgroundWorker fertig ist?
Update Die Leute scheinen von dieser Frage verwirrt zu sein. Sie scheinen zu glauben, dass ich den BackgroundWorker verwenden werde als:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);
Das ist es nicht , das ist nicht was ich tue und das ist nicht was hier gefragt wird. Wenn dies der Fall wäre, wäre es sinnlos, einen Hintergrundarbeiter einzusetzen.
quelle
((BackgroundWorker)sender).CancellationPending
, um das Stornierungsereignis zu erhaltenBei dieser Antwort liegt ein Problem vor . Die Benutzeroberfläche muss weiterhin Nachrichten verarbeiten, während Sie warten. Andernfalls wird sie nicht neu gestrichen. Dies ist ein Problem, wenn Ihr Hintergrundmitarbeiter lange braucht, um auf die Abbruchanforderung zu antworten.
Ein zweiter Fehler ist, dass er
_resetEvent.Set()
niemals aufgerufen wird, wenn der Worker-Thread eine Ausnahme auslöst - der Haupt-Thread bleibt unbegrenzt warten -, dieser Fehler kann jedoch leicht mit einem try / finally-Block behoben werden.Eine Möglichkeit, dies zu tun, besteht darin, ein modales Dialogfeld mit einem Timer anzuzeigen, der wiederholt prüft, ob der Hintergrundarbeiter die Arbeit beendet hat (oder in Ihrem Fall den Abbruch beendet hat). Sobald der Hintergrund-Worker fertig ist, gibt der modale Dialog die Kontrolle an Ihre Anwendung zurück. Der Benutzer kann erst dann mit der Benutzeroberfläche interagieren.
Eine andere Methode (vorausgesetzt, Sie haben maximal ein modellloses Fenster geöffnet) besteht darin, ActiveForm.Enabled = false festzulegen und dann die Anwendung DoEvents zu durchlaufen, bis der Hintergrund-Worker den Abbruch abgeschlossen hat. Anschließend können Sie ActiveForm.Enabled = true erneut festlegen.
quelle
Fast alle von Ihnen sind verwirrt von der Frage und verstehen nicht, wie ein Arbeiter eingesetzt wird.
Betrachten Sie einen RunWorkerComplete-Ereignishandler:
Und alles ist gut.
Jetzt kommt eine Situation, in der der Anrufer den Countdown abbrechen muss, weil er eine Selbstzerstörung der Rakete im Notfall ausführen muss.
Und es gibt auch eine Situation, in der wir die Zugangstore zur Rakete öffnen müssen, aber nicht während eines Countdowns:
Und schließlich müssen wir die Rakete tanken, aber das ist während eines Countdowns nicht erlaubt:
Ohne die Möglichkeit, auf das Abbrechen eines Workers zu warten, müssen alle drei Methoden in das RunWorkerCompletedEvent verschoben werden:
Jetzt könnte ich meinen Code so schreiben, aber ich werde es einfach nicht tun. Es ist mir egal, ich bin es einfach nicht.
quelle
Sie können in den RunWorkerCompletedEventArgs im RunWorkerCompletedEventHandler nachsehen , wie der Status war. Erfolg, abgebrochen oder ein Fehler.
Update : Um zu sehen, ob Ihr Worker .CancelAsync () aufgerufen hat, verwenden Sie Folgendes:
quelle
Sie warten nicht auf den Abschluss des Hintergrundarbeiters. Das macht den Zweck, einen separaten Thread zu starten, ziemlich zunichte. Stattdessen sollten Sie Ihre Methode beenden lassen und jeden Code, der von der Fertigstellung abhängt, an einen anderen Ort verschieben. Sie lassen sich vom Mitarbeiter sagen, wann es fertig ist, und rufen dann den verbleibenden Code auf.
Wenn Sie warten möchten, bis etwas abgeschlossen ist, verwenden Sie ein anderes Threading-Konstrukt, das einen WaitHandle bereitstellt.
quelle
Warum können Sie sich nicht einfach an das BackgroundWorker.RunWorkerCompleted-Ereignis binden? Es handelt sich um einen Rückruf, der "auftritt, wenn der Hintergrundvorgang abgeschlossen, abgebrochen oder eine Ausnahme ausgelöst wurde".
quelle
Ich verstehe nicht, warum Sie auf den Abschluss eines BackgroundWorker warten möchten. es scheint wirklich das genaue Gegenteil der Motivation für die Klasse zu sein.
Sie können jedoch jede Methode mit einem Aufruf von worker.IsBusy starten und sie beenden lassen, wenn sie ausgeführt wird.
quelle
Ich möchte nur sagen, dass ich hierher gekommen bin, weil ich einen Hintergrundarbeiter brauche, der wartet, während ich einen asynchronen Prozess in einer Schleife ausführe. Mein Fix war viel einfacher als all diese anderen Dinge ^^
Ich dachte nur, ich würde teilen, weil ich hier auf der Suche nach einer Lösung gelandet bin. Dies ist auch mein erster Beitrag zum Stapelüberlauf. Wenn es also schlecht ist oder irgendetwas, würde ich Kritiker lieben! :) :)
quelle
Hm, vielleicht verstehe ich deine Frage nicht richtig.
Der Hintergrundarbeiter ruft das WorkerCompleted-Ereignis auf, sobald seine 'Arbeitsmethode' (die Methode / Funktion / Sub, die das Hintergrundworker.doWork-Ereignis behandelt ) abgeschlossen ist, sodass nicht überprüft werden muss, ob das BW noch ausgeführt wird. Wenn Sie Ihren Worker stoppen möchten, überprüfen Sie die Eigenschaft "Stornierung anstehend" in Ihrer "Worker-Methode".
quelle
Der Workflow eines
BackgroundWorker
Objekts erfordert grundsätzlich, dass Sie dasRunWorkerCompleted
Ereignis sowohl für Anwendungsfälle mit normaler Ausführung als auch für Benutzerabbruch behandeln. Aus diesem Grund ist die Eigenschaft RunWorkerCompletedEventArgs.Cancelled vorhanden. Grundsätzlich erfordert dies, dass Sie Ihre Cancel-Methode als eigenständige asynchrone Methode betrachten.Hier ist ein Beispiel:
Wenn Sie wirklich nicht möchten, dass Ihre Methode beendet wird, würde ich vorschlagen, ein Flag wie ein
AutoResetEvent
auf ein abgeleitetes zu setzenBackgroundWorker
und dann zu überschreibenOnRunWorkerCompleted
, um das Flag zu setzen. Es ist immer noch etwas klobig; Ich würde empfehlen, das Abbruchereignis wie eine asynchrone Methode zu behandeln und alles zu tun, was gerade imRunWorkerCompleted
Handler ausgeführt wird.quelle
Ich bin ein wenig zu spät zur Party hier (ungefähr 4 Jahre), aber was ist mit dem Einrichten eines asynchronen Threads, der eine Besetztschleife verarbeiten kann, ohne die Benutzeroberfläche zu sperren? Dann muss der Rückruf von diesem Thread die Bestätigung sein, dass der BackgroundWorker den Abbruch beendet hat ?
Etwas wie das:
Im Wesentlichen wird dadurch ein anderer Thread ausgelöst, der im Hintergrund ausgeführt wird und nur in der Besetztschleife wartet, um festzustellen, ob der
MyWorker
Vorgang abgeschlossen ist. SobaldMyWorker
der Abbruch abgeschlossen ist, wird der Thread beendet und wir können damitAsyncCallback
jede Methode ausführen, die wir benötigen, um den erfolgreichen Abbruch zu verfolgen - es funktioniert wie ein Pseudo-Ereignis. Da dies vom UI-Thread getrennt ist, wird die UI nicht gesperrt, während wir darauf warten, dassMyWorker
der Abbruch abgeschlossen ist. Wenn Sie wirklich beabsichtigen, zu sperren und auf den Abbruch zu warten, ist dies für Sie nutzlos. Wenn Sie jedoch nur warten möchten, damit Sie einen anderen Prozess starten können, funktioniert dies einwandfrei.quelle
Ich weiß, dass dies sehr spät ist (5 Jahre), aber Sie suchen nach einem Thread und einem SynchronizationContext . Sie müssen UI-Aufrufe "von Hand" an den UI-Thread zurückstellen, anstatt das Framework dies automatisch tun zu lassen.
Auf diese Weise können Sie einen Thread verwenden, auf den Sie bei Bedarf warten können.
quelle
quelle
Fredrik Kalseths Lösung für dieses Problem ist die beste, die ich bisher gefunden habe. Andere Lösungen
Application.DoEvent()
können Probleme verursachen oder funktionieren einfach nicht. Lassen Sie mich seine Lösung in eine wiederverwendbare Klasse umwandeln. DaBackgroundWorker
es nicht versiegelt ist, können wir unsere Klasse daraus ableiten:Mit Flags und korrekter Verriegelung stellen wir sicher, dass das
_resetEvent.WaitOne()
wirklich nur aufgerufen wird, wenn einige Arbeiten gestartet wurden, sonst_resetEvent.Set();
möglicherweise nie aufgerufen werden!Das try-finally stellt sicher, dass
_resetEvent.Set();
es aufgerufen wird, auch wenn in unserem DoWork-Handler eine Ausnahme auftreten sollte. Andernfalls könnte die Anwendung beim Aufrufen für immer einfrierenCancelSync
!Wir würden es so verwenden:
Sie können dem
RunWorkerCompleted
Ereignis auch einen Handler hinzufügen, wie hier gezeigt:BackgroundWorker Class (Microsoft-Dokumentation) .
quelle
Durch das Schließen des Formulars wird meine geöffnete Protokolldatei geschlossen. Mein Hintergrundarbeiter schreibt diese Protokolldatei, sodass ich sie erst
MainWin_FormClosing()
beenden kann, wenn mein Hintergrundarbeiter beendet ist. Wenn ich nicht auf die Beendigung meines Hintergrundarbeiters warte, treten Ausnahmen auf.Warum ist das so schwer?
Ein einfaches
Thread.Sleep(1500)
funktioniert, verzögert jedoch das Herunterfahren (wenn es zu lang ist) oder verursacht Ausnahmen (wenn es zu kurz ist).Verwenden Sie zum Herunterfahren direkt nach dem Beenden des Hintergrundarbeiters einfach eine Variable. Das funktioniert bei mir:
quelle
Sie können das RunWorkerCompleted-Ereignis beenden. Selbst wenn Sie bereits einen Ereignishandler für _worker hinzugefügt haben, können Sie einen weiteren hinzufügen, der in der Reihenfolge ausgeführt wird, in der er hinzugefügt wurde.
Dies kann nützlich sein, wenn Sie mehrere Gründe haben, warum ein Abbruch auftreten kann, wodurch die Logik eines einzelnen RunWorkerCompleted-Handlers komplizierter wird als gewünscht. Abbrechen, wenn ein Benutzer versucht, das Formular zu schließen:
quelle
Ich benutze die
async
Methode undawait
warte, bis der Arbeiter seine Arbeit beendet hat:und in
DoWork
Methode:Sie können auch die kapseln
while
Schleife inDoWork
mittry ... catch
zu Satz_isBusy
istfalse
auf Ausnahme. Oder checken Sie einfach_worker.IsBusy
dieStopAsync
while-Schleife ein.Hier ist ein Beispiel für die vollständige Implementierung:
So stoppen Sie den Arbeiter und warten, bis er bis zum Ende läuft:
Die Probleme mit dieser Methode sind:
quelle
Oh Mann, einige davon sind lächerlich komplex geworden. Sie müssen lediglich die Eigenschaft BackgroundWorker.CancellationPending im DoWork-Handler überprüfen. Sie können es jederzeit überprüfen. Sobald es aussteht, setzen Sie e.Cancel = True und beenden Sie die Methode.
// Methode hier private void Worker_DoWork (Objektabsender, DoWorkEventArgs e) {BackgroundWorker bw = (Absender als BackgroundWorker);
}}
quelle