如何正确停止BackgroundWorker

我有一个2combobox的forms。 我想要填充combobox2.DataSource基于combobox1.Textcombobox2.Text (我假设用户已经完成在combobox1input,并在combobox2input中间)。 所以我有一个combobox2事件处理程序是这样的:

 private void combobox2_TextChanged(object sender, EventArgs e) { if (cmbDataSourceExtractor.IsBusy) cmbDataSourceExtractor.CancelAsync(); var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text, V2 = combobox2.Text}; cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); } 

至于构buildDataSource是耗时的过程(它创build一个对数据库的请求并执行它),我决定最好在另一个使用BackgroundWorker的进程中执行它。 因此,当cmbDataSourceExtractor尚未完成其工作并且用户再次input一个符号时,就会出现这种情况。 在这种情况下,我在这一行上得到一个exception
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); 关于BackgroundWorker繁忙,不能在同一时间执行多个操作。
如何摆脱这个exception?
提前致谢!

CancelAsync实际上不会中止你的线程或类似的东西。 它向工作线程发送一条消息,通过BackgroundWorker.CancellationPending来取消工作。 您在后台运行的DoWork委托必须定期检查该属性并处理取消本身。

棘手的部分是你的DoWork委托可能是阻塞的,这意味着你在你的DataSource上做的工作必须完成,然后才能做任何事情(比如检查CancellationPending)。 您可能需要将您的实际工作移至另一个asynchronous委托(或者更好的方式是将工作提交给ThreadPool ),并让您的主工作线程轮询,直到此内部工作线程触发等待状态,或检测到CancellationPending。

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx

http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx

如果你在CancelAsync()和RunWorkerAsync()之间添加一个循环,就可以解决你的问题

  private void combobox2_TextChanged(object sender, EventArgs e) { if (cmbDataSourceExtractor.IsBusy) cmbDataSourceExtractor.CancelAsync(); while(cmbDataSourceExtractor.IsBusy) Application.DoEvents(); var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text, V2 = combobox2.Text}; cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); } 

调用Application.DoEvents()的while循环将会拖延新工作线程的执行,直到当前的工作线程被正确取消为止。请记住,您仍然需要处理取消工作线程。 像这样的东西:

  private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e) { if (this.cmbDataSourceExtractor.CancellationPending) { e.Cancel = true; return; } // do stuff... } 

第一个代码片段中的Application.DoEvents()将继续处理您的GUI线程消息队列,所以甚至取消并更新cmbDataSourceExtractor.IsBusy属性仍将被处理(如果您只是添加继续而不是Application.DoEvents()循环会将GUI线程locking为繁忙状态,并不会处理该事件以更新cmbDataSourceExtractor.IsBusy)

您必须使用主线程和BackgroundWorker之间共享的标志,例如BackgroundWorker.CancellationPending 。 当你想让BackgroundWorker退出时,只需使用BackgroundWorker.CancelAsync()来设置标志。

MSDN有一个示例: http : //msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx

我的例子。 DoWork如下:

  DoLengthyWork(); //this is never executed if(bgWorker.CancellationPending) { MessageBox.Show("Up to here? ..."); e.Cancel = true; } 

里面DoLenghtyWork:

 public void DoLenghtyWork() { OtherStuff(); for(int i=0 ; i<10000000; i++) { int j = i/3; } } 

在OtherStuff()中:

 public void OtherStuff() { for(int i=0 ; i<10000000; i++) { int j = i/3; } } 

你想要做的是修改DoLenghtyWork和OtherStuff(),使它们变成:

 public void DoLenghtyWork() { if(!bgWorker.CancellationPending) { OtherStuff(); for(int i=0 ; i<10000000; i++) { int j = i/3; } } } public void OtherStuff() { if(!bgWorker.CancellationPending) { for(int i=0 ; i<10000000; i++) { int j = i/3; } } } 

问题是由于cmbDataSourceExtractor.CancelAsync()是一个asynchronous方法,当cmdDataSourceExtractor.RunWorkerAsync(...)退出时Cancel操作尚未完成。 在再次调用RunWorkerAsync之前,您应该等待cmdDataSourceExtractor完成。 如何做到这一点在这个SO问题中解释。

我的答案有点不同,因为我已经尝试了这些方法,但没有奏效。 我的代码使用额外的类来检查公共静态类中的布尔标志,因为数据库值被读取,或者在对象被添加到List对象之前我更喜欢它。 请参阅下面的代码中的更改。 我添加了ThreadWatcher.StopThread属性。 对于这个解释我nog去恢复当前的线程,因为这不是你的问题,但是这很容易设置属性为false之前访问下一个线程…

 private void combobox2_TextChanged(object sender, EventArgs e) { //Stop the thread here with this ThreadWatcher.StopThread = true;//the rest of this thread will run normally after the database function has stopped. if (cmbDataSourceExtractor.IsBusy) cmbDataSourceExtractor.CancelAsync(); while(cmbDataSourceExtractor.IsBusy) Application.DoEvents(); var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text, V2 = combobox2.Text}; cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); } 

一切都好

 private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e) { if (this.cmbDataSourceExtractor.CancellationPending) { e.Cancel = true; return; } // do stuff... } 

现在添加下面的类

 public static class ThreadWatcher { public static bool StopThread { get; set; } } 

并在你的class级阅读数据库

 List<SomeObject>list = new List<SomeObject>(); ... if (!reader.IsDbNull(0)) something = reader.getString(0); someobject = new someobject(something); if (ThreadWatcher.StopThread == true) break; list.Add(something); ... 

不要忘记使用finally块来正确closures数据库连接等。希望这有助于! 如果您觉得有帮助,请给我打电话。

我同意你们的看法 但是有时你必须添加更多的东西。

IE

1)添加这个worker.WorkerSupportsCancellation = true;

2)给你添加一些方法来做以下事情

 public void KillMe() { worker.CancelAsync(); worker.Dispose(); worker = null; GC.Collect(); } 

所以在closures你的应用程序之前,你必须调用这个方法。

3)大概你可以Dispose, null BackgroundWorker内部的所有variables和定时器。

在我的情况下,我不得不汇集数据库的付款确认进来,然后更新WPF用户界面。

整个过程的机制:

 public void Execute(object parameter) { try { var amount = ViewModel.Amount; var transactionId = ViewModel.TransactionMain.TransactionId.ToString(); var productCode = ViewModel.TransactionMain.TransactionDetailList.First().Product.ProductCode; var transactionReference = GetToken(amount, transactionId, productCode); var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, transactionReference); Process.Start(new ProcessStartInfo(url)); ViewModel.UpdateUiWhenDoneWithPayment = new BackgroundWorker {WorkerSupportsCancellation = true}; ViewModel.UpdateUiWhenDoneWithPayment.DoWork += ViewModel.updateUiWhenDoneWithPayment_DoWork; ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerCompleted += ViewModel.updateUiWhenDoneWithPayment_RunWorkerCompleted; ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerAsync(); } catch (Exception e) { ViewModel.Log.Error("Failed to navigate to payments", e); MessageBox.Show("Failed to navigate to payments"); } } 

检查完成的机制:

  private void updateUiWhenDoneWithPayment_DoWork(object sender, DoWorkEventArgs e) { Thread.Sleep(30000); while (string.IsNullOrEmpty(GetAuthToken()) && !((BackgroundWorker)sender).CancellationPending) { Thread.Sleep(5000); } //Plug in pooling mechanism this.AuthCode = GetAuthToken(); } 

如果窗口closures,则取消机制:

 private void PaymentView_OnUnloaded(object sender, RoutedEventArgs e) { var context = DataContext as PaymentViewModel; if (context.UpdateUiWhenDoneWithPayment != null && context.UpdateUiWhenDoneWithPayment.WorkerSupportsCancellation && context.UpdateUiWhenDoneWithPayment.IsBusy) context.UpdateUiWhenDoneWithPayment.CancelAsync(); }