如何等待BackgroundWorker取消?

考虑一个假设的对象的方法,为你做的东西:

public class DoesStuff { BackgroundWorker _worker = new BackgroundWorker(); ... public void CancelDoingStuff() { _worker.CancelAsync(); //todo: Figure out a way to wait for BackgroundWorker to be cancelled. } } 

如何等待BackgroundWorker完成?


过去人们曾经尝试过:

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

但是, 这种死锁 ,因为在处理RunWorkerCompleted事件之前, IsBusy不会被清除,并且该事件在应用程序空闲之前无法处理。 应用程序不会闲置,直到工人完成。 (另外,这是一个繁忙的循环 – 恶心)。

其他人已经添加了build议:

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

这个问题就是Application.DoEvents()导致当前队列中的消息被处理,从而导致重入问题(.NET不可重入)。

我希望使用一些涉及事件同步对象的解决scheme,其中代码等待事件 – 工作人员的RunWorkerCompleted事件处理程序设置。 就像是:

 Event _workerDoneEvent = new WaitHandle(); public void CancelDoingStuff() { _worker.CancelAsync(); _workerDoneEvent.WaitOne(); } private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e) { _workerDoneEvent.SetEvent(); } 

但是我回到了僵局:事件处理程序无法运行,直到应用程序空闲,并且应用程序不会因为等待事件而闲置。

那么如何等待BackgroundWorker完成呢?


更新人们似乎对这个问题感到困惑。 他们似乎认为我将使用BackgroundWorker作为:

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

不是这样,那不是我正在做的事情, 也不是这里所要问的。 如果是这样的话,那么使用后台工作者就没有意义了。

如果我理解你的要求,你可以做这样的事情(代码没有经过testing,但显示了一般的想法):

 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 } 

这个回应有一个问题。 用户界面需要在等待的时候继续处理消息,否则不会重新绘制,如果后台工作人员花费很长时间来响应取消请求,这将是一个问题。

第二个缺点是,如果工作线程抛出一个exception,那么_resetEvent.Set()永远不会被调用 – 让主线程无限期地等待 – 但是这个缺陷很容易通过try / finally块来解决。

一种方法是显示一个模式对话框,其中有一个计时器,可以反复检查后台工作人员是否已经完成工作(或在您的情况下完成取消)。 一旦后台工作完成,模式对话框将控制返回到您的应用程序。 直到发生这种情况,用户才能与UI进行交互。

另一种方法(假设您最多打开一个无模式窗口)是设置ActiveForm.Enabled = false,然后在Application,DoEvents上循环,直到后台工作者完成取消,之后您可以再次设置ActiveForm.Enabled = true。

几乎所有人都对这个问题感到困惑,并不了解一个工人是如何使用的。

考虑一个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; } 

一切都很好。

现在来了一个情况,呼叫者需要中止倒计时,因为他们需要执行火箭的紧急自毁。

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

还有一种情况是我们需要打开火箭的进入门,而不是倒计时:

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

最后,我们需要去除火箭的燃料,但在倒计时期间是不允许的。

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

如果没有能力等待工人取消,我们必须将所有三个方法移到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(); } 

现在我可以写这样的代码,但我只是不会。 我不在乎,我只是不在乎。

您可以检查RunWorkerCompletedEventHandler中的RunWorkerCompletedEventArgs以查看状态。 成功,取消或错误。

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

更新 :要查看您的工作人员是否通过使用以下命令调用.CancelAsync():

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

不要等待后台工作人员完成。 这几乎打败了启动一个单独的线程的目的。 相反,你应该让你的方法完成,并将任何依赖完成的代码移到不同的地方。 你让工人告诉你什么时候完成,然后调用剩余的代码。

如果您想等待某个东西完成,请使用提供WaitHandle的另一个线程构造。

为什么你不能只绑定到BackgroundWorker.RunWorkerCompleted事件。 这是一个callback,它会在后台操作完成,被取消或者引发exception时发生。

我不明白你为什么要等待BackgroundWorker完成; 它真的好像与class级的动机完全相反。

但是,您可以通过调用worker.IsBusy来启动每个方法,并在运行时退出。

只是想说,我来到这里,因为我需要一个后台工作人员等待,而我正在运行一个asynchronous过程,而在一个循环中,我的修复比其他所有的东西更容易^^

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

刚刚想到我会分享,因为这是我在寻找解决scheme时结束了。 另外,这是我在堆栈溢出的第一篇文章,所以如果它不好或者我喜欢批评的话! 🙂

嗯,也许我没有得到你的问题。

一旦他的'workermethod'(处理backgroundworker.doWork-event的方法/函数/子)完成,backgroundworker将调用WorkerCompleted事件,因此不需要检查BW是否仍在运行。 如果您想停止您的工作人员检查'工作人员方法'中的取消挂起的属性 。

BackgroundWorker对象的工作stream程基本上要求您为正常执行和用户取消用例处理RunWorkerCompleted事件。 这就是为什么RunWorkerCompletedEventArgs.Cancelled属性存在的原因。 基本上,这样做需要你认为你的Cancel方法本身就是一个asynchronous方法。

这是一个例子:

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

如果你真的不希望你的方法退出,我build议在派生的BackgroundWorker上放置一个像AutoResetEvent这样的标志,然后重写OnRunWorkerCompleted来设置标志。 尽pipe如此,它仍然是一种可笑的东西。 我build议像asynchronous方法一样对待cancel事件,并在RunWorkerCompleted处理程序中执行当前正在执行的操作。

我在这里(约4年)晚了一点,但如何设置一个asynchronous线程,可以处理一个繁忙的循环,而不lockingUI,然后从该线程的callback是确认BackgroundWorker已经完成取消?

像这样的东西:

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

从本质上来说,这是为了让另一个线程在后台运行,只是在繁忙的循环中等待MyWorker是否完成。 一旦MyWorker完成取消,线程将退出,我们可以使用它的AsyncCallback来执行我们需要遵循的成功取消的方法 – 它将像伪造事件一样工作。 由于这与UI线程是分开的,因此在等待MyWorker完成取消时,它不会locking用户界面。 如果你的意图是locking和等待取消,那么这对你来说是没有用的,但是如果你只是想等待,那么你可以开始另一个过程,那么这很好地工作。

我知道这真的很晚(5年),但是你正在寻找的是使用一个线程和一个SynchronizationContext 。 您将不得不将UI调用封送到UI线程中,而不是让框架自动执行。

这使您可以使用可以等待的线程(如果需要的话)。

 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 

Fredrik Kalseth对这个问题的解决scheme是迄今为止我发现的最好的解决scheme。 其他解决scheme使用Application.DoEvent() ,可能会导致问题或根本不工作。 让我把他的解决scheme投入到一个可重用的类中。 由于BackgroundWorker不是封闭的,我们可以从它派生出我们的类:

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

使用标志和正确的locking,我们确保_resetEvent.WaitOne()真的只在一些工作已经开始时被调用,否则_resetEvent.Set(); 可能从未被称为!

try-finally确保_resetEvent.Set(); 将被调用,即使我们的DoWork处理程序中发生exception。 否则,在调用CancelSync时,应用程序可能永远冻结!

我们会这样使用它:

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

您也可以将一个处理程序添加到RunWorkerCompleted事件中,如下所示:
BackgroundWorker类 (Microsoft文档)

closures窗体closures我打开的日志文件。 我的后台工作者写这个日志文件,所以我不能让MainWin_FormClosing()完成,直到我的后台工作终止。 如果我不等待我的后台工作者终止,则发生exception。

为什么这么难?

一个简单的Thread.Sleep(1500)工作,但延迟关机(如果太长),或导致exception(如果太短)。

要在后台工作程序终止后立即closures,只需使用一个variables即可。 这对我有用:

 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.) 

哦,其中一些已经变得非常复杂了。 您只需检查DoWork处理程序中的BackgroundWorker.CancellationPending属性即可。 你可以随时检查它。 一旦挂起,设置e.Cancel = True并从方法中保释。

//方法在这里private void Worker_DoWork(object sender,DoWorkEventArgs e){BackgroundWorker bw =(sender as BackgroundWorker);

 // do stuff if(bw.CancellationPending) { e.Cancel = True; return; } // do other stuff 

}