取消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时中止后台操作。”
这里发生了什么?它是否是线程安全的?
答案 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”的方法。方法返回值不会通过调用代码缓存(可能是因为确定方法是否有副作用太难)。