C# BackgroundWorker.CancellationPending线程如何安全?
取消BackgroundWorker操作的方法是调用C# BackgroundWorker.CancellationPending线程如何安全?,c#,.net,multithreading,thread-safety,backgroundworker,C#,.net,Multithreading,Thread Safety,Backgroundworker,取消BackgroundWorker操作的方法是调用BackgroundWorker.CancelAsync(): 在BackgroundWorker.DoWork事件处理程序中,我们检查BackgroundWorker.CancellationPending: // RUNNING IN WORKER THREAD void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { while (!backgroundWor
BackgroundWorker.CancelAsync()
:
在BackgroundWorker.DoWork事件处理程序中,我们检查BackgroundWorker.CancellationPending
:
// RUNNING IN WORKER THREAD
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!backgroundWorker.CancellationPending) {
DoSomething();
}
}
上述想法在网络上随处可见,包括在互联网上
现在,我的问题是:这个线程到底是如何安全的
我已经研究了ILSpy中的BackgroundWorker类-CancelAsync()
只将cancellationPending设置为true而不使用内存屏障,cancellationPending
只返回cancellationPending而不使用内存屏障
根据,上述情况不是线程安全的。但是说,“这个属性是供工作线程使用的,它应该定期检查CancellationPending,并在设置为true时中止后台操作。”
这是怎么回事?它是否线程安全?这是一个很好的观点,也是一个很好的疑问。听起来好像不是线程安全的。我认为MSDN也有同样的疑问,这就是为什么这是有根据的 小心 请注意,DoWork事件处理程序中的代码可能会完成其任务 正在发出取消请求,并且您的轮询循环 可能无法将CancellationPending设置为true。在这种情况下 中System.ComponentModel.RunWorkerCompletedEventArgs的取消标志 您的RunWorkerCompleted事件处理程序将不会设置为true,甚至 尽管有人提出取消申请。这种情况被称为 竞态条件和是多线程编程中常见的问题。 有关多线程设计问题的更多信息,请参阅托管 线程最佳实践 我不确定BackgroundWorker类的实现。在这种情况下,查看它如何在内部使用属性很重要 它是线程安全的。
代码
while (!backgroundWorker.CancellationPending)
正在读取属性,而编译器知道它无法缓存结果
由于在普通框架中,每次写入都与VolatileWrite相同,CancelAsync()方法只能设置一个字段 这是因为BackgroundWorker继承自从MarshalByRefObject继承的组件。MBRO对象可以驻留在另一台机器上,也可以驻留在另一个进程或appdomain中。这是通过让代理模拟对象来实现的,代理具有完全相同的属性和方法,但其实现跨线路封送调用
这样做的一个副作用是抖动不能内联方法和属性,这将破坏代理。这还可以防止在cpu寄存器中存储字段值的任何优化。就像volatile一样。这个问题可以用两种方式来解释: 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”的方法。调用代码不会缓存方法返回值(可能是因为计算方法是否有副作用太难)。我明白了,但我主要担心的是backgroundWorker.CancellationPending中的
backgroundWorker.CancelAsync()可能永远不会计算为true由于缓存,已调用代码>。如果是这样,无论如何都不会引发RunWorkerCompleted事件。这不是OP所要求的。您描述的问题是由于工作线程可能在有机会在事件的args中设置取消标志之前结束。OP询问是否可以安全地假设不同的线程可以在设置标志后读取该标志的当前值。我认为该属性阻止了值的缓存,这使得它完全是线程安全的。嗯,很有趣。我没有想到房产可能会有所不同。我想知道这是否在任何地方都有记录……然后,BackgroundWorker.CancelAsync()
再次将字段而不是属性设置为true,因此我猜可能存在只写入缓存的问题。我的理解是,该问题专门询问CancellationPending的backing字段,而不是属性本身(该属性是该语言的一个功能,从CLR的角度来看,它只是一个方法)。那么,是什么阻止了后台字段通过读取线程进行缓存?@Dmitry-它是私有的事实(cancellationPending,小写首字母)。客户端代码必须使用该属性。作为咨询,您认为在这种情况下,我在回答中描述的场景即使在弱内存模型上也不会发生吗?@Dmitry-声明它为volatile没有什么区别,它已经被视为volatile。@Dmitry-我在回答中解释了这一点。很抱歉,我解释得不够好,主题在您了解机器代码以及CPU寄存器的重要性之前,volatile的定义是相当难以理解的。
while (!backgroundWorker.CancellationPending) {
DoSomething();
}