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();
}
}
以上想法就结束了网络,包括 MSDN 页面后台工作人员。
现在,我的问题是:这到底是如何保证线程安全的?
我查看了 ILSpy 中的BackgroundWorker 类 - CancelAsync()
只是将cancelationPending 设置为true,而不使用内存屏障,而CancellationPending
只是返回cancelationPending,而不使用内存屏障。
根据此 Jon Skeet 页面,以上内容不是< /em> 线程安全。但是BackgroundWorker.CancellationPending的文档说, “此属性供工作线程使用,该线程应定期检查 CancellationPending 并在其设置为 true 时中止后台操作。”
这是怎么回事?它是线程安全的还是非线程安全的?
The way to cancel a BackgroundWorker's operation is to call BackgroundWorker.CancelAsync()
:
// RUNNING IN UI THREAD
private void cancelButton_Click(object sender, EventArgs e)
{
backgroundWorker.CancelAsync();
}
In a BackgroundWorker.DoWork event handler, we check BackgroundWorker.CancellationPending
:
// RUNNING IN WORKER THREAD
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!backgroundWorker.CancellationPending) {
DoSomething();
}
}
The above idea is all over the web, including on the MSDN page for BackgroundWorker.
Now, my question is this: How on earth is this thread-safe?
I've looked at the BackgroundWorker class in ILSpy — CancelAsync()
simply sets cancellationPending to true without using a memory barrier, and CancellationPending
simply returns cancellationPending without using a memory barrier.
According to this Jon Skeet page, the above is not thread-safe. But the documentation for BackgroundWorker.CancellationPending says, "This property is meant for use by the worker thread, which should periodically check CancellationPending and abort the background operation when it is set to true."
What's going on here? Is it thread-safe or not?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这是因为BackgroundWorker继承自Component,而Component又继承自MarshalByRefObject。 MBRO 对象可能驻留在另一台机器上或另一个进程或应用程序域中。其工作原理是让对象由代理模拟,该代理具有所有完全相同的属性和方法,但其实现通过网络封送调用。
这样做的一个副作用是抖动无法内联方法和属性,这会破坏代理。这也阻止了将字段值存储在 CPU 寄存器中的任何优化。就像 volatility 一样。
It is because BackgroundWorker inherits from Component which inherits from MarshalByRefObject. An MBRO object may reside on another machine or in another process or appdomain. That works by having the object impersonated by a proxy that has all of the exact same properties and methods but whose implementations marshal the call across the wire.
One side effect of that is that the jitter cannot inline methods and properties, that would break the proxy. Which also prevents any optimizations from being made that stores the field value in a cpu register. Just like volatile does.
它是线程安全的。
该代码
正在读取属性,并且编译器知道它无法缓存结果。
由于在正常框架中,每个 Write 都与 VolatileWrite 相同,因此 CancelAsync() 方法可以只设置一个字段。
It is thread-safe.
The code
is reading a property and the compiler knows it can't cache the result.
And since in the normal framework every Write is the same as VolatileWrite, the CancelAsync() method can just set a field.
问题可以用两种方式解释:
1)BackgroundWorker.CancellationPending实现正确吗?
这是不正确的,因为它可能会导致取消请求被忽视。该实现使用从支持字段进行的普通读取。如果此支持字段被其他线程更新,则更新可能对读取代码不可见。这就是实现的样子:
正确的实现将尝试读取最新的值。这可以通过使用内存屏障或锁将支持字段声明为“易失性”来实现。可能还有其他选项,其中一些比其他选项更好,但这取决于拥有“BackgroundWorker”类的团队。
2) 使用BackgroundWorker.CancellationPending 的代码是否正确?
这段代码是正确的。循环将一直旋转,直到 CancellationPending 返回“true”。请记住,C# 属性只是 CLR 方法的语法糖。在 IL 级别,这只是另一种类似于“get_CancellationPending”的方法。方法返回值不会通过调用代码进行缓存(可能是因为判断方法是否有副作用太难)。
Question can be interpreted in two ways:
1) Is BackgroundWorker.CancellationPending implementation correct?
It is not correct because it may result in cancellation request being unnoticed. The implementation uses ordinary read from the backing field. If this backing field is updated by other threads then the update may be invisible to the reading code. This is what implementation looks like:
The correct implementation would try to read the most up to date value. This can be achieved by declaring backing field 'volatile', using memory barrier or lock. There are probably other options and some of them are better than the others but is up to the team that owns 'BackgroundWorker' class.
2) Is code that uses BackgroundWorker.CancellationPending correct?
This code is correct. The loop will spin until CancellationPending returns 'true'. Keeping in mind that C# properties is just a syntax sugar for CLR methods. At the IL level this is just another method that will look like "get_CancellationPending". Method return values are not cached by calling code (probably because figuring whether method has side effects is too hard).
一个很好的观点,也是一个非常合理的怀疑。听起来好像不是线程安全的。我认为 MSDN 也有同样的疑问,这就是为什么它出现在 下BackgroundWorker.CancelAsync 方法。
注意
我不确定 BackgroundWorker 类的实现。查看它如何使用 BackgroundWorker.WorkerSupportsCancellation在这种情况下,内部属性很重要。
A good point and a very fair doubt. It sounds like not thread safe. I think MSDN also has the same doubt and that's why this is there under BackgroundWorker.CancelAsync Method.
Caution
I am not sure about BackgroundWorker class implementation. Looking at how it is using BackgroundWorker.WorkerSupportsCancellation property internally is important in this case.