如何正确停止BackgroundWorker

时间:2011-01-19 07:26:56

标签: c# .net winforms backgroundworker


我有一个有2个组合框的表格。我想基于combobox2.DataSourcecombobox1.Text来填充combobox2.Text(我假设用户已在combobox1中完成了输入,并且正在输入combobox2 })。所以我有一个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 );
}

至于构建DataSource是一个耗时的过程(它创建了对数据库的请求并执行它)我决定最好使用BackgroundWorker在另一个进程中执行它。因此,当cmbDataSourceExtractor尚未完成其工作并且用户再键入一个符号时,会出现这种情况。在这种情况下,我在这一行得到例外 cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );关于BackgroundWorker正忙,无法在同一时间执行多项操作 如何摆脱这种例外?
提前谢谢!

8 个答案:

答案 0 :(得分:88)

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

棘手的部分是您的DoWork委托可能已阻止,这意味着您在DataSource上所做的工作必须先完成,然后才能执行其他操作(例如检查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 :(得分:27)

如果你在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属性的偶数(如果您只是添加了一个continue而不是Application。 DoEvents()循环会将GUI线程锁定为忙状态,并且不会处理事件来更新cmbDataSourceExtractor.IsBusy)

答案 2 :(得分:6)

您必须使用主线程和BackgroundWorker之间共享的标志,例如BackgroundWorker.CancellationPending。如果希望BackgroundWorker退出,只需使用BackgroundWorker.CancelAsync()设置标志。

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

答案 3 :(得分:3)

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

答案 4 :(得分:1)

问题是由cmbDataSourceExtractor.CancelAsync()是异步方法,Cancel操作在cmdDataSourceExtractor.RunWorkerAsync(...)出口时尚未完成的事实引起的。在再次致电cmdDataSourceExtractor之前,您应该等待RunWorkerAsync完成。如何解释in this SO question

答案 5 :(得分:1)

在我的情况下,我必须汇总数据库以进行付款确认,然后更新property用户界面。

推动所有流程的机制:

WPF

检查完成的机制:

public void Execute(object parameter)
        {
            try
            {
                var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, "transactionRef");
                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();
    }

答案 6 :(得分:1)

我的回答有点不同,因为我尝试过这些方法,但它们没有用。我的代码使用一个额外的类来检查公共静态类中的布尔标志,因为在将对象添加到List对象或类似事件之前,读取数据库值或者我喜欢它。请参阅下面代码中的更改。我添加了ThreadWatcher.StopThread属性。对于这个解释,我将恢复当前的线程,因为它不是你的问题,但在访问下一个线程之前就像将属性设置为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);
...
不要忘记使用finally块来正确关闭数据库连接等。希望这会有所帮助!如果您觉得有用,请注意我。

答案 7 :(得分:0)

我同意这些人的意见。但有时你必须添加更多东西。

IE

1)添加此worker.WorkerSupportsCancellation = true;

2)为你的课程添加一些方法来做以下事情

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

因此,在关闭您的申请之前,您必须调用此方法。

3)您可以Dispose, null BackgroundWorker内的所有变量和计时器。{3>}。