考虑一个对象的假设方法,为您做一些事情:
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); }
但是这种死锁,因为IsBusy
在RunWorkerCompleted
事件处理之后才会被清除,并且在应用程序空闲之前无法处理该事件.在工人完成之前,应用程序不会闲置.(另外,这是一个繁忙的循环 - 恶心.)
其他人已添加建议将其融入:
while (_worker.IsBusy) { Application.DoEvents(); }
这样做的问题是Application.DoEvents()
导致当前队列中的消息被处理,这会导致重入问题(.NET不可重入).
我希望使用一些涉及事件同步对象的解决方案,其中代码等待事件 - 工作者的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);
那不是它,这不是我正在做的事情,这不是在这里被问到的.如果是这种情况,使用后台工作者就没有意义了.
如果我理解你的要求是正确的,你可以做这样的事情(代码没有经过测试,但显示了一般的想法):
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 }
此响应存在问题.UI需要在您等待时继续处理消息,否则它将不会重新绘制,如果您的后台工作程序需要很长时间来响应取消请求,这将是一个问题.
第二个缺陷是,_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(); }
如果没有等待worker取消的能力,我们必须将所有三个方法移动到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(); }
现在我可以像这样写我的代码,但我不会.我不在乎,我不是.