我们都知道在多线程环境中处理.NET事件时的问题。 其中之一是当我们尝试调用事件而不复制到局部变量时:
if (MyEvent != null)
MyEvent(this, EventArgs.Empty);
在这种情况下,如果在一个线程检查MyEvent!= null之后,我们可以获得竞争条件,另一个是从事件中取消订阅处理程序。 (然后MyEvent尝试触发和操作.. NullRefException)
解决方案(由J.Richter提出)是将事件处理程序复制到局部变量:
var handler = MyEvent;
if (handler != null)
handler(this, EventArgs.Empty);
这很有效,因为
Delegates are immutable; once created, the invocation list of a delegate does not change.
但我知道AMD64 JIT does some optimizations可以忽略本地副本并读取事件处理程序的实际值。 (一篇文章陈旧但我找不到任何关于此类问题的实际信息。)
那么,在这种情况下CLR JIT实际上是如何工作的?可以有NullReferenceException吗?
答案 0 :(得分:2)
博客文章不完整,它没有讲述他们对此做了什么。它是旧的,在x64抖动实际发布前一年发布。他们可能在测试时发现了这个问题。
他断言应该使用 volatile 并不完全不准确。然而,这需要用C编译器眼镜来查看问题。或者x86抖动实现volatile的方式。不幸的是,C#语言严重破坏了 volatile 的定义,随意拉出以处理具有弱内存模型的处理器。 Itanium是那里的主要麻烦制造者。搞砸了足以让Joe Duffy完全放弃并declare it evil。
他们提出的解决方案相当激烈,他们完全消除了对 volatile 的需求,它根本不会影响代码生成。并且事件发射模式在骑行中被拯救,x64抖动实际上确实复制并存储了参考。不是在局部变量中,而是在CPU寄存器中,x64有很多。否则是标准优化器功能。