重复使用一个BackgroundWorker,取消并等待它完成

4
假设您有一个搜索文本框,并附加了一个在TextChanged事件下运行的搜索算法,该算法使用BackgroundWorker。如果文本框中出现新字符,则需要取消先前的搜索并重新运行。
我尝试在主线程和bgw之间使用事件(参见this previous question),但仍然会出现“当前正忙,无法同时运行多个任务”的错误。
    BackgroundWorker bgw_Search = new BackgroundWorker();
    bgw_Search.DoWork += new DoWorkEventHandler(bgw_Search_DoWork);

    private AutoResetEvent _resetEvent = new AutoResetEvent(false);

    private void txtSearch_TextChanged(object sender, EventArgs e)
    {
        SearchWithBgw();
    }

    private void SearchWithBgw()
    {
        // cancel previous search
        if (bgw_Search.IsBusy)
        {
            bgw_Search.CancelAsync();

            // wait for the bgw to finish, so it can be reused.
            _resetEvent.WaitOne(); // will block until _resetEvent.Set() call made
        }

        // start new search
        bgw_Search.RunWorkerAsync();   // error "cannot run multiple tasks concurrently"
    }

    void bgw_Search_DoWork(object sender, DoWorkEventArgs e)
    {
        Search(txtSearch.Text, e);
    }

    private void Search(string aQuery, DoWorkEventArgs e)
    {
        int i = 1;            
        while (i < 3)             // simulating search processing...
        {
            Thread.Sleep(1000);                           
            i++;

            if (bgw_Search.CancellationPending)
            {
                _resetEvent.Set(); // signal that worker is done
                e.Cancel = true;
                return;
            }
        }
    }

为了反映答案,请不要重复使用BackgroundWorker,创建一个新的:

    private void SearchWithBgw()
    {   
        if (bgw_Search.IsBusy)
        {
            bgw_Search.CancelAsync();
            _resetEvent.WaitOne(); // will block until _resetEvent.Set() call made

            bgw_Search = new BackgroundWorker();
            bgw_Search.WorkerSupportsCancellation = true;
            bgw_Search.DoWork += new DoWorkEventHandler(bgw_Search_DoWork);
        }

        bgw_Search.RunWorkerAsync();        
    }
4个回答

8
当_resetEvent.WaitOne()调用完成后,工作线程实际上并没有完成。它正在从DoWork()返回并等待运行RunWorkerCompleted事件(如果有的话)。这需要时间。
没有可靠的方法可以确保BGW以同步方式完成。阻塞IsBusy或等待RunWorkerCompleted事件运行会导致死锁。如果你确实想只使用一个BGW,则必须将请求排队。或者别太纠结,再分配一个BGW。它们成本非常低廉。

2
如果旧的后台工作者存在,则创建一个新的后台工作者。
private void SearchWithBgw()
{
    // cancel previous search
    if (bgw_Search.IsBusy)
    {
        bgw_Search.CancelAsync();

        // wait for the bgw to finish, so it can be reused.
        _resetEvent.WaitOne(); // will block until _resetEvent.Set() call made
        BackgroundWorker bgw_Search = new BackgroundWorker();
        bgw_Search.DoWork += new DoWorkEventHandler(bgw_Search_DoWork);


    }

    // start new search
    bgw_Search.RunWorkerAsync();   // error "cannot run multiple tasks concurrently"
}

我知道你放了假代码,但是当代码正常完成时,你也需要确保设置_resetEvent


如果我在这个函数中创建一个 new BackgroundWorker(),我该如何取消之前的工作?我应该将工作线程保存在列表中吗?由于搜索功能会更新控件,我不希望多个工作线程更新同一个控件。 - Carlos Torres
@Carlos,你取消旧的,并等待它在创建新的之前标记_resetEvent。 - Scott Chamberlain

2
  • 不要重复使用 Backgroundworker。它是一种廉价的资源,而不是线程。
  • 确保你的 Bgw 代码停止,你的代码看起来没问题。Bgw 将释放线程到池中。
  • 但同时,为新的任务创建一个新的任务/ Bgw。
  • 您可能希望取消订阅旧 Bgw 的 Completed 事件。

1

我认为你应该考虑不取消后台工作程序。

如果你取消请求,而用户打字速度比服务器返回查询的速度更快,他将无法看到建议,直到他完成打字。

在这种交互式场景中,最好显示与用户输入相对滞后的响应。你的用户会知道,如果他心中所想的单词出现在你的建议列表中,他可以停止输入。

当服务器繁忙时,这对你的服务器也更好,因为你实际使用的响应较少,而不是许多被取消但最终未显示的请求,这些请求会产生一些成本。

我在(3D)渲染应用程序中遇到了类似的问题,初学者的错误是在每次鼠标移动时取消并重新渲染。这导致了大量的计算和很少的交互反馈。


+1 谢谢,说得好。我感觉需要取消是因为我在后台工作中更新了GUI,我不想让多个工作线程更新同一个控件。 - Carlos Torres

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接