4

在有关方法的MSDN 文章BackgroundWorker.CancelAsync中,有一条警告说:

请注意,DoWork事件处理程序中的代码可能会在发出取消请求时完成其工作,并且您的轮询循环可能会错过CancellationPending设置为true。在这种情况下,即使发出了取消请求,事件处理程序中 的Cancelled标志 也不会设置为true 。这种情况称为 竞争条件,是多线程编程中常见的问题。System.ComponentModel.RunWorkerCompletedEventArgsRunWorkerCompleted

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

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

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

1 回答 1

4

没有办法避免它,不是每个线程问题都有解决方案。当你把它们推到极端时,这些事情是可以推理出来的。想象一下,您的 UI 线程中的代码在工作线程完成前一纳秒调用了 CancelAsync() 。就像线程正在执行它的最后一条机器代码指令一样。显然,线程不可能检测到这一点。UI线程也没有任何可能的方式来查看线程是否在其最后一条指令上。

这不是一个真正的问题,只需在编写 RunWorkerCompleted 事件处理程序时记住它。如果 e.Cancelled 属性为 false,则工作人员无论如何都完成了。如有必要,您可以在调用 CancelAsync() 时将其与另一个设置为 true 的布尔值简单地 &&。大致:

    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
            //...
        }
    }

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

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

于 2012-04-26T08:34:56.107 回答