使用BackgroundWorker.CancelAsync时如何避免竞争条件?

时间:2012-04-26 05:14:19

标签: .net backgroundworker race-condition

MSDN article关于BackgroundWorker.CancelAsync方法中,有一个警告说:

  

请注意DoWork事件处理程序中的代码可能会完成它   正在进行取消请求,以及您的轮询循环   可能会将CancellationPending设置为 true 。在这种情况下,   Cancelled的标志   你的System.ComponentModel.RunWorkerCompletedEventArgs   RunWorkerCompleted事件处理程序不会设置为 true   虽然取消了请求。这种情况称为a   竞争条件,是多线程编程中常见的问题。

避免此竞争条件的正确和良好解决方案是什么?

这是我的示例代码,它引发了这种情况的出现:

public partial class MainWindow : Window
{
    BackgroundWorker bW;
    int bWsCount = 0;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_MouseMove(object sender, MouseEventArgs e)
    {
        if (bW != null)
            bW.CancelAsync();
        bW = new BackgroundWorker();
        bW.WorkerSupportsCancellation = true;
        bW.DoWork += new DoWorkEventHandler(bW_DoWork);
        bW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bW_RunWorkerCompleted);
        bW.RunWorkerAsync(0);
        bWsCount++;
        labelBackgroundWorkersCount.Content = bWsCount;
    }

    void bW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        if (e.Cancelled || worker.CancellationPending)
        {
            bWsCount--;
            labelBackgroundWorkersCount.Content = bWsCount;
        }
        else
            worker.RunWorkerAsync(1000);
    }

    void bW_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        int sleepDuration = Convert.ToInt32(e.Argument);
        if (worker.CancellationPending) { e.Cancel = true; return; }
        Thread.Sleep(sleepDuration);
        if (worker.CancellationPending) { e.Cancel = true; return; }
    }
}

1 个答案:

答案 0 :(得分:4)

没有办法避免它,并非每个线程问题都有解决方案。当你把它们推到极致时,可以推理出这些东西。想象一下,在工作线程完成之前,UI线程中的代码会将CancelAsync()调用纳秒。正如线程正在执行其最后的机器代码指令一样。很明显,线程无法检测到这种情况。也没有任何可能的方法让UI线程看到线程在最后一条指令上。

这不是一个真正的问题,只需在编写RunWorkerCompleted事件处理程序时牢记这一点。如果e.Cancelled属性为false,则无论如何都要完成工作。如果有必要,你可以简单地和&&当你调用CancelAsync()时,你设置为true的另一个bool。大致是:

    private bool cancelRequested;

    private void CancelButton_Click(object sender, EventArgs e) {
        cancelRequested = true;
        backgroundWorker1.CancelAsync();
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        if (e.Error != null) throw e.Error;
        if (!e.Cancelled && !cancelRequested) {
            // Completed entirely normally
            //...
        }
    }

请记住,这不仅仅是代码竞赛。更实际的问题是,这也是用户大脑中的竞赛。使用故障模式,在用户单击“取消”按钮之前,一切都正常完成。

因此,就用户所知,操作被取消,而事实上并非如此。这可能会产生令人不快的后果,您必须通过告诉用户真正发生了什么来处理这些后果。在这种情况下,使用代码片段没有任何意义,如果取消操作并允许它完成,它也可以正常工作。