触发事件时避免竞争条件(在多线程应用程序中)的常见做法是:
EventHandler<EventArgs> temp = SomeEvent;
if (temp != null) temp(e);
"Remember that delegates are immutable and this is why this technique works in theory. However, what a lot of developers don't realize is that this code could be optimized by the compiler to remove the local temp variable entirely. If this happens, this version of the code is identical to the first version, so a NullReferenceException is still possible."
问题(根据这本书)是“编译器可以优化这段代码,以完全删除本地临时变量。如果发生这种情况,则此版本的代码与第一个版本相同,因此仍然可能出现nullreferenceexception。”
根据clr via c_,这里有一种更好的方法来强制编译器复制事件指针。
virtual void OnNewMail(NewMailEventArgs e)
{
EventHandler<NewMailEventArgs> temp =
Interlocked.CompareExchange(ref NewMail, null, null);
if (temp != null)
temp(this, e);
}
在这里,compareexchange将newmail引用更改为null(如果该引用为null),如果该引用不为null,则不更改newmail。换句话说,compareexchange根本不会更改newmail中的值,但它确实以原子的、线程安全的方式返回newmail中的值。
里克特,杰弗里(2010-02-12)。通过C_的CLR(第265页)。奥利里媒体-A.Kindle版。
我在.net 4.0framework上,不知道这怎么可能工作,因为interlocked.compareexchange需要一个对位置的引用,而不是对事件的引用。
不是书中有错,就是我误解了。有人实施过这种方法吗?或者有更好的方法来防止这里的种族状况?
更新
这是我的错,密码起作用了。我只是指定了错误的选角,但是根据Bradley(下面)的说法,在.NET2.0和Windows上没有必要这样做。
最佳答案
编译器(或jit)不允许优化if/temp
以外的内容(在clr 2.0和更高版本中);不允许引入从堆中读取的内容(规则2)。
因此,不能再次读取MyEvent
;必须在temp
语句中读取if
的值。
请参见CLR 2.0 Memory Model了解有关这种情况的详细讨论,并解释为什么标准模式是好的。
但是,如果您运行的是不提供CLR 2.0内存模型保证的非Microsoft CLR(例如Mono),或者运行的是Itanium(它的硬件内存模型非常弱),则需要使用类似Richter的代码来消除潜在的竞争条件。
关于Interlocked.CompareExchange
的问题,语法public event EventHandler<NewMailEventArgs> NewMail
只是c语法糖,用于声明EventHandler<NewMailEventArgs>
类型的私有字段和具有add
和remove
方法的公共事件。Interlocked.CompareExchange
调用读取privateEventHandler<NewMailEventArgs>
字段的值,因此这段代码确实按照richter描述的那样编译和工作;这在microsoftcrl中是不必要的。