BackgroundWorker.CancellationPending是如何线程安全的?

时间:2011-08-06 11:43:51

标签: c# .net multithreading thread-safety backgroundworker

取消BackgroundWorker操作的方法是调用BackgroundWorker.CancelAsync()

// RUNNING IN UI THREAD
private void cancelButton_Click(object sender, EventArgs e)
{
    backgroundWorker.CancelAsync();
}

在BackgroundWorker.DoWork事件处理程序中,我们检查BackgroundWorker.CancellationPending

// RUNNING IN WORKER THREAD
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    while (!backgroundWorker.CancellationPending) {
        DoSomething();
    }
}

上述想法遍布网络,包括the MSDN page for BackgroundWorker

现在,我的问题是:这个线程安全怎么样?

我查看了ILSpy中的BackgroundWorker类 - CancelAsync()只是将cancellationPending设置为true而不使用内存屏障,CancellationPending只返回cancellationPending而不使用内存屏障。

根据this Jon Skeet page,上面的不是线程安全的。但是the documentation for BackgroundWorker.CancellationPending说,“这个属性是供工作线程使用的,它应该定期检查CancellationPending并在设置为true时中止后台操作。”

这里发生了什么?它是否是线程安全的?

4 个答案:

答案 0 :(得分:12)

这是因为BackgroundWorker从继承自MarshalByRefObject的Component继承。 MBRO对象可以驻留在另一台机器上,也可以驻留在另一个进程或应用程序域中。这可以通过代理模拟对象来实现,该代理具有所有完全相同的属性和方法,但其实现通过网络封送调用。

其中一个副作用是抖动无法内联方法和属性,这会破坏代理。这也阻止了将字段值存储在cpu寄存器中的任何优化。就像volatile一样。

答案 1 :(得分:8)

它是线程安全的 代码

  while (!backgroundWorker.CancellationPending) 

正在读取属性,编译器知道它无法缓存结果。

因为在正常框架中,每个Write都与VolatileWrite相同,所以CancelAsync()方法只能设置一个字段。

答案 2 :(得分:0)

一个好点和一个非常公平的怀疑。这听起来不是线程安全的。我认为MSDN也有同样的疑问,这就是BackgroundWorker.CancelAsync Method下的原因。

<强>注意

  

请注意,DoWork事件处理程序中的代码可能会完成   正在进行取消请求,以及您的轮询循环   可能会错过CancellationPending设置为true。在这种情况下,   取消了System.ComponentModel.RunWorkerCompletedEventArgs中的标志   即使您的RunWorkerCompleted事件处理程序也不会设置为true   虽然取消了请求。这种情况称为a   竞争条件,是多线程编程中的常见问题。   有关多线程设计问题的更多信息,请参阅托管   线程最佳实践。

我不确定BackgroundWorker类的实现。在这种情况下,查看内部如何使用BackgroundWorker.WorkerSupportsCancellation属性非常重要。

答案 3 :(得分:0)

问题可以用两种方式解释:

1)BackgroundWorker.CancellationPending 实施是否正确?

这是不正确的,因为它可能导致取消请求被忽视。该实现使用来自支持字段的普通读取。如果此后备字段由其他线程更新,则更新可能对读取代码不可见。这就是实现的样子:

// non volatile variable:
private Boolean cancellationPending;

public Boolean CancellationPending {
    get {
        // ordinary read:
        return cancellationPending;
    }
}

正确的实现将尝试读取最新值。这可以通过使用内存屏障或锁定声明支持字段“volatile”来实现。可能还有其他选择,其中一些比其他选项更好,但是由拥有'BackgroundWorker'类的团队决定。

2)使用 BackgroundWorker.CancellationPending的代码是否正确?

while (!backgroundWorker.CancellationPending) {
    DoSomething();
}

此代码是正确的。循环将旋转,直到CancellationPending返回'true'。请记住,C#属性只是CLR方法的语法糖。在IL级别,这只是另一种看起来像“get_CancellationPending”的方法。方法返回值不会通过调用代码缓存(可能是因为确定方法是否有副作用太难)。

相关问题