如何正确停止BackgroundWorker

问题描述:


我有一个窗体上有2个组合框。我想根据combobox1.Textcombobox2.Text填写combobox2.DataSource(我假设用户已完成combobox1的输入,并且正在输入combobox2的中间)。所以,我对于combobox2像这样的事件处理程序:如何正确停止BackgroundWorker

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

至于建筑的DataSource是耗时的过程(它会创建一个请求数据库,并执行它),我决定,这是更好地在另一个进程中执行它使用BackgroundWorker。因此,当cmbDataSourceExtractor尚未完成其工作并且用户再次键入另一个符号时会出现这种情况。在这种情况下,我在此行上发现异常
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues);有关该BackgroundWorker正忙,无法在同一时间执行多个操作。
如何摆脱这个异常?
在此先感谢!

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

棘手的部分是你的DoWork委托可能是阻塞,这意味着你在你的数据源做的工作必须完成,然后才能做任何事情(如检查CancellationPending)。您可能需要您的实际工作转移到另一个异步委托(或者更好的是,提交工作到ThreadPool),和你的主要工作线程轮询直到内部工作线程触发等待状态,或者它检测CancellationPending。

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

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

+1

我想补充一点,CancelAsync是无阻塞的操作,即使您处理您的DoWork正确CancellationPendning(),你不能继续下一行的DoWork(执行CancelAsync)在几秒钟内可以终止。 – dzendras 2011-01-19 07:36:19

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

MSDN具有样品:http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx

问题是由以下事实:cmbDataSourceExtractor.CancelAsync()是一个异步方法造成的,Cancel操作还未完成时cmdDataSourceExtractor.RunWorkerAsync(...) exitst。在再次致电RunWorkerAsync之前,您应该等待cmdDataSourceExtractor完成。如何解释in this SO question

如果你喜欢这样就解决您的问题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循环)将hault的执行你的新工作者线程直到当前的线程被正确取消,记住你仍然需要处理取消你的工作线程。喜欢的东西:

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

的Application.DoEvents()在第一个代码段会继续处理您的GUI线程的消息队列,因此,即使取消和更新cmbDataSourceExtractor.IsBusy属性将被处理(如果你只需添加一个继续而不是应用程序。DoEvents()循环会将GUI线程锁定为繁忙状态,并且不会处理事件以更新cmbDataSourceExtractor.IsBusy)

+0

因为没有Application.DoEvents()在WPF我不能使用此解决方案。当新的请求到达时,工作人员仍然忙时,我设置了附加标志。然后当该标志被设置时,我从RunWorkerCompleted回调的主线程上调用RunWorkerAsync。 – bor 2013-06-05 01:17:57

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

IE

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

2)添加到您类中的一些方法做以下的事情

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

所以之前关闭应用程序你必须调用此方法。

3)也许你可以Dispose, nullBackgroundWorker里面的所有变量和计时器。

+2

看到这里,为什么就没有必要调用Dispose手动一个BackgroundWorker http://*.com/questions/2542326/proper-way-to-dispose-of-a-backgroundworker – 2012-06-21 15:26:38

+2

只是为了节省的Google一对夫妇的调试运行中,如果你的员工有一个循环/或使用RunWorkerCompletedEventHandler并检查取消,你将需要移动处置,空和收集方法进行到RunWorkerCompletedEventHandler。 – HockeyJ 2012-11-27 16:30:16

我的例子。 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; 
     } 
    } 
} 

在我的情况下,我不得不汇集数据库进行付款确认,然后更新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(); 
    } 

机制,如果窗口被关闭,其抵消:

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

我的答案有点不同,因为我尝试了这些方法,但他们没有奏效。我的代码使用额外的类来检查公共静态类中的布尔标志,因为数据库值被读取,或者在将对象添加到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; } 
} 

和你的类,你读取数据库

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

不要忘记使用一个最后阻止正确关闭你的dat基地连接等希望这有助于!如果您觉得有用,请给我打电话。