在多线程环境中引发事件

Tom*_*mas 5 .net c# concurrency multithreading memory-model

从 .NET 4.0 开始,自动生成的添加/删除事件处理程序是线程安全的(此处此处)。因此,将其侦听器注册到公开事件的客户端可以从多个线程同时进行,而不会发生竞争。

但是如果我想以线程安全的方式触发事件怎么办?推荐的做法似乎如下(这里):

public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
    EventHandler myEvent = MyEvent;
    if (myEvent != null)
    {
        myEvent(this, e);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,在阅读了有关 .NET 内存模型的内容后(例如 MSDN 杂志2012-122013-01),我不再认为这是正确的。我担心的是编译器可能会引入内存读取,因此上面的代码可能会被 JIT-ted 变成这样:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

删除局部变量并使用重复的内存读取是合法的,因为如果在单线程环境中执行它不会改变方法的行为。这是 ECMA 规范 ( ECMA-335: I.12.6.4 )。MSDN杂志2013-01期也提供了可理解的例子。

我在这里错过了什么吗?如果没有,请建议解决方法。

小智 2

您必须添加唯一的一行以使第一个片段在多线程环境中正确:

public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
    EventHandler myEvent = MyEvent;
    Thread.MemoryBarrier();
    if (myEvent != null)
    {
        myEvent(this, e);
    }
}
Run Code Online (Sandbox Code Playgroud)

内存屏障拒绝对编译器和 CPU 的读写重新排序。这就是易失性读/写的实现方式。您可以在此处阅读有关内存屏障的更多信息。