5

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

但是如果我想以线程安全的方式触发事件呢?推荐的做法似乎如下(此处):

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

但是,在阅读了有关 .NET 内存模型的一些内容(例如 MSDN 杂志2012-122013-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期也提供了可理解的示例。

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

4

1 回答 1

2

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

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

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

于 2013-08-30T12:20:04.197 回答