如何正确停止BackgroundWorker


69


我有一个带有2个组合框的表单。我想combobox2.DataSource根据combobox1.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很忙,无法同时执行多个操作。
如何摆脱这种例外?
提前致谢!

Answers:


95

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

棘手的部分是您的DoWork委托可能正在阻塞,这意味着您必须先完成对DataSource的工作,然后才能执行其他任何操作(例如检查CancellationPending)。您可能需要将实际工作移至另一个异步委托(或者可能更好的方法是,将工作提交给ThreadPool),并进行主工作线程轮询,直到此内部工作线程触发等待状态,或者它检测到CancellationPending。

http://msdn.microsoft.com/zh-CN/library/system.componentmodel.backgroundworker.cancelasync.aspx

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


1
我还要补充说CancelAsync是一个非阻塞操作,因此即使您在DoWork()中正确处理了CancellationPendning,您也无法在下一行执行CancelAsync,因为DoWork()可能会在几秒钟后终止。
dzendras 2011年

32

如果您在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)


我无法使用此解决方案,因为WPF中没有Application.DoEvents()。当新请求到达时,工作人员仍然很忙时,我设置了其他标志。然后,当设置标志时,我从RunWorkerCompleted回调在主线程上调用RunWorkerAsync。
2013年


2

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

8
我知道这是旧的,但是这太错了...您正在检查取消之前是否正在等待任何工作.....?
泰勒·尼科尔斯

1
正如@TylerNichols所说,此检查必须在for循环内,否则它将不会停止,尽管我认为这是OP想要的
ivcubr

1

问题是由以下事实引起的:这cmbDataSourceExtractor.CancelAsync()是一种异步方法,退出Cancel时操作尚未完成cmdDataSourceExtractor.RunWorkerAsync(...)。您应该等待cmdDataSourceExtractor完成才能RunWorkerAsync再次致电。如何执行此操作在此SO问题中进行了说明


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块来正确关闭数据库连接等。希望这会有所帮助!如果您认为有帮助,请标记我。


1

就我而言,我必须合并数据库以进行付款确认,然后更新WPFUI。

加速所有过程的机制:

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

窗口关闭时取消的机制:

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

0

我同意你们的看法。但是有时您必须添加更多内容。

IE浏览器

1)添加 worker.WorkerSupportsCancellation = true;

2)向您的班级添加一些方法来执行以下操作

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

因此,在关闭应用程序之前,您必须调用此方法。

3)可能您可以在中包含Dispose, null所有变量和计时器BackgroundWorker


2
看到这里,为什么就没有必要手动调用Dispose为一个BackgroundWorker stackoverflow.com/questions/2542326/...
Amc_rtty

3
只是为了节省谷歌用户几次调试的时间,如果您的工作人员有循环/或使用RunWorkerCompletedEventHandler并正在检查取消,则需要将dispose,null和Collect方法移至RunWorkerCompletedEventHandler。
HockeyJ 2012年

1
永远不要调用GC.Collect(),因为它会主动通过整个应用程序的对象图,这会破坏具有大量对象的应用程序的性能。
Vedran
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.