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

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

上述想法遍布整个网络,包括在BackgroundWorker的MSDN页面上 。

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

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

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

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

它是线程安全的。
代码

  while (!backgroundWorker.CancellationPending) 

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

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

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

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

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

警告

请注意,DoWork事件处理程序中的代码可能会在发出取消请求时完成其工作,并且您的轮询循环可能会将CancellationPending设置为true。 在这种情况下,即使发出取消请求,RunWorkerCompleted事件处理程序中System.ComponentModel.RunWorkerCompletedEventArgs的Canceled标志也不会设置为true。 这种情况称为竞争条件,是multithreading编程中的常见问题。 有关multithreading设计问题的更多信息,请参阅托管线程最佳实践。

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

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

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”的方法。 方法返回值不通过调用代码缓存(可能是因为确定方法是否有副作用太难)。