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 吗?

4

1 回答 1

2

博客文章不完整,它没有讲述他们对此做了什么。它很旧,在 x64 抖动实际发布前一年发布。他们可能在测试时发现了问题。

他关于应该使用volatile的断言并非完全不准确。然而,这需要使用 C 编译器来查看问题。或者 x86 抖动实现 volatile 的方式。不幸的是,C# 语言严重破坏了volatile的定义,被随意拉出以处理具有弱内存模型的处理器。安腾是那里的主要麻烦制造者。搞砸了,让乔达菲完全放弃并宣布它是邪恶的。

他们提出的解决方案相当激进,他们完全消除了对volatile的需求,它对代码生成完全没有影响。并且事件触发模式被拯救了,x64 抖动实际上复制并存储了参考。不是在局部变量中而是在 CPU 寄存器中,x64 有很多。否则为标准优化器功能。

于 2013-10-14T18:49:35.970 回答