事件调用模式和CLR AMD64 JIT优化

4
我们都知道在多线程环境中处理.NET事件时会遇到问题。其中之一是在不将事件复制到本地变量的情况下尝试调用事件:
if (MyEvent != null)
    MyEvent(this, EventArgs.Empty);

在这种情况下,如果一个线程检查到MyEvent != null,而另一个线程取消订阅了该事件的处理程序,就会出现竞争条件。(然后MyEvent试图触发并且出现了NullRefException)
解决方案(由J.Richter提出)是将事件处理程序复制到本地变量中:
var handler = MyEvent;
if (handler != null)
    handler(this, EventArgs.Empty); 

这个方法很好,因为

委托是不可变的;一旦创建,委托的调用列表就不会改变。

但是我知道AMD64 JIT做了一些优化,可以忽略本地副本并读取事件处理程序的实际值。(这篇文章有点老,但我找不到任何关于这个问题的最新信息)。

那么,在这种情况下,CLR JIT实际上是如何工作的?会出现NullReferenceException吗?


IMO事件完全不适合于多线程环境。 - spender
1
@spender 你为什么会这样建议呢?在多线程环境中,只要使用正确,事件仍然可以非常有用。许多线程组件完全基于事件... - Reed Copsey
1个回答

2
这篇博客不完整,没有说他们对此采取了什么措施。它很旧,发布于x64 Jitter实际发布之前的一年。他们可能是在测试时发现了这个问题。
他主张使用volatile并非完全不准确。然而,这需要用C编译器眼镜来看待问题。或者以x86 Jitter实现volatile的方式来看待问题。不幸的是,C#语言存在一个严重破损的volatile定义,是任意拉出来应对具有弱内存模型的处理器的。Itanium是那里的主要麻烦制造者。足够糟糕,让乔·达菲(Joe Duffy)彻底放弃,并宣布其为魔鬼
他们提出的解决方案相当激进,他们完全消除了volatile的需求,它对代码生成没有任何影响。事件触发模式也因此得到了拯救,x64 Jitter实际上会复制和存储引用。不是在局部变量中,而是在CPU寄存器中,x64有足够多的寄存器。否则就是标准的优化器特性。

优化是否违法,还是CLR有效地扩展了规范以使此模式安全? - usr
如果您让volatile意味着您想要的任何内容,那么这并不违法。就像博客文章作者所做的那样。在这里,CLR没有涉及,这是一个纯粹的Jitter细节。 - Hans Passant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接