事件和多线程再次

时间:2016-05-11 20:50:09

标签: c# .net multithreading language-lawyer memory-model

我担心看似标准的前C#6模式的正确性会引发事件:

EventHandler localCopy = SomeEvent;
if (localCopy != null)
    localCopy(this, args);

我读过Eric Lippert的Events and races,并且知道调用陈旧事件处理程序还有一个问题,但我担心的是编译器/ JITter是否允许优化本地副本,有效地重写代码为

if (SomeEvent != null)
    SomeEvent(this, args);

可能NullReferenceException

根据C#语言规范,§3.10,

  

必须保留这些副作用的顺序的关键执行点是对volatile字段(第10.5.3节),锁定语句(第8.12节)以及线程创建和终止的引用。

- 因此上述模式中没有关键执行点,优化器也不受此限制。

相关answer by Jon Skeet(2009年)州

  

由于条件的原因,JIT不允许在第一部分中执行您正在讨论的优化。我知道这是作为一个幽灵提出的,但它无效。 (我刚才和Joe Duffy或Vance Morrison一起检查过;我不记得是哪一个。)

- 但是评论引用了这篇博客文章(2008年):Events and Threads (Part 4),它基本上说CLR 2.0的JITter(可能是后续版本?)不能引入读取或写入,所以必须没有Microsoft .NET下的问题。但这似乎没有提及其他.NET实现。

[附注:我没有看到不引入读取证明了所述模式的正确性。难道JITter只是在其他一些局部变量中看到SomeEvent的一些陈旧值,并优化其中一个读取,而不是另一个?完全合法,对吗?]

此外,这篇MSDN文章(2012年):Igor Ostrovsky撰写的The C# Memory Model in Theory and Practice声明如下:

  

非重新排序优化某些编译器优化可能会引入或消除某些内存操作。例如,编译器可能用一次读取替换字段的重复读取。类似地,如果代码读取字段并将值存储在局部变量中然后重复读取变量,编译器可以选择重复读取字段

     

由于ECMA C#规范不排除非重新排序优化,因此可能会允许它们。实际上,正如我将在第2部分中讨论的那样,JIT编译器确实执行了这些类型的优化。

这似乎与Jon Skeet的答案相矛盾。

由于现在C#不再是仅支持Windows的语言,因此问题是模式的有效性是否是当前CLR实现中有限的JITter优化的结果,或者是语言的预期属性。

所以,问题是如下:从C#-the-language的角度讨论的模式是否有效?(这意味着是否需要语言编译器/运行时来禁止某种类型优化。)

当然,欢迎关于该主题的规范性参考文献。

1 个答案:

答案 0 :(得分:1)

不允许优化器转换存储在局部变量中的代码模式,该变量稍后用于将该变量的所有用法都用作初始化它的原始表达式。这不是一个有效的转换,所以它不是"优化"。表达式可能导致或依赖于副作用,因此表达式需要运行,存储在某处,然后在指定时使用。如果您的代码只执行一次,那么运行时将无效转换为将事件解析为委托两次。

就重新订购而言;对于多线程,操作的重新排序非常复杂,但这种模式的重点在于您现在在单线程上下文中执行相关逻辑 / em>的。事件的值存储在本地,并且对于在其他线程中运行的任何代码,可以或多或少任意地对该读取进行排序,但是将该值读取到局部变量中不能重新排序到< em>同一个线程的后续操作,即if检查或该代理的调用。

鉴于此,该模式确实做了它打算做的事情,即获取事件的快照并调用所有处理程序(如果有的话),而不会因为没有任何处理程序而抛出NRE。