从.NET 4.0开始,自动生成的添加/删除事件处理程序是线程安全的(here和here)。因此,将他们的侦听器注册到公开事件的客户端可以在没有比赛的情况下从多个线程同时执行此操作。
但是,如果我想以线程安全的方式触发事件呢?建议的做法似乎如下(here):
public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
EventHandler myEvent = MyEvent;
if (myEvent != null)
{
myEvent(this, e);
}
}
但是,阅读了有关.NET内存模型的内容(例如MSDN杂志2012-12和2013-01),我不再认为这是正确的。我担心的是内存读取可能是由编译器引入的,所以上面的代码可以被JIT引用到这样的东西:
public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
// JIT removed the local variable and introduced two memory reads instead.
if (MyEvent != null)
{
// A race condition may cause the following line to throw a NullReferenceException.
MyEvent(this, e);
}
}
删除局部变量并使用重复的内存读取是合法的,因为如果在单线程环境中执行,它不会改变方法的行为。这是ECMA规范(ECMA-335: I.12.6.4)。 MSDN杂志的2013-01期刊中也提供了可理解的例子。
我在这里遗漏了什么吗?如果没有,请告知解决方法。
答案 0 :(得分:2)
您必须添加唯一一行才能在多线程环境中使第一个代码段正确无误:
public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
EventHandler myEvent = MyEvent;
Thread.MemoryBarrier();
if (myEvent != null)
{
myEvent(this, e);
}
}
内存屏障拒绝为编译器和CPU重新排序读取和写入。这就是实现易失性读/写的方式。您可以阅读更多about memory barrier here。