事件提升和.net 4.5中的阅读介绍

Pav*_*nin 2 .net c# events multithreading

我有与此MSDN杂志文章相关的问题.

阅读简介正如我刚才所解释的,编译器有时会将多个读取融合为一个.编译器还可以将单个读取拆分为多个读取.在.NET Framework 4.5中,读取介绍比读取消除要少得多,并且仅在非常罕见的特定情况下发生.但是,它有时会发生.

public class ReadIntro {
  private Object _obj = new Object();
  void PrintObj() {
    Object obj = _obj;
    if (obj != null) {
      Console.WriteLine(obj.ToString());
    // May throw a NullReferenceException
    }
  }
  void Uninitialize() {
    _obj = null;
  }
}
Run Code Online (Sandbox Code Playgroud)

如果检查PrintObj方法,obj.ToString表达式中的obj值看起来永远不会为null.但是,该行代码实际上可能会抛出NullReferenceException.CLR JIT可能会编译PrintObj方法,就好像它是这样编写的:

void PrintObj() {
  if (_obj != null) {
    Console.WriteLine(_obj.ToString());
  }
}
Run Code Online (Sandbox Code Playgroud)

但这不是一个与事件合作的模式吗?!

void RaiseEvent()
{
    var myEvent = MyEvent;
    if (myEvent != null)
    {
         myEvent(this, EventArgs.Empty);
    }
}
Run Code Online (Sandbox Code Playgroud)

我想念一些重要的东西吗?

jod*_*ods 5

这篇文章让我感到困惑,我做了一些研究.我发现了两种思想流派.

有人说这种模式是安全的

因为CLR 2.0内存模型比1.x更严格并且禁止它.

"读取和写入无法引入",MSDN杂志(10月05日),文章了解低锁技术在多线程应用中的影响.

".NET内存模型禁止它[读取介绍]参考GC堆内存的普通变量",Joe Duffy,Windows上的Concurrent Programming,pp517-8.

[注意:Joe Duffy基本上说的是同样的东西但是留下了在堆栈上读取引入的可能性,因此不共享因此安全]

我发现那些".NET 2.0内存模型"的解释很奇怪.我已经阅读了2012 ECMA CLI规范以及C#标准,没有发现禁止读取引入的声明.他们不太可能在2.0和4之间削弱内存模型.(??)

另一方面,我认为JIT团队已经意识到这些模式,并且不会破坏它们,至少在x86上......但是说这与它在标准中的说法不同.团队决策可能在将来或其他平台上发生变化.

编辑不要错过Eric Lippert的评论如下:"no read introduction"是Microsoft CLI实现的承诺.ECMA标准中没有任何内容,当使用其他实现(例如Mono)时,所有投注均已关闭.结束编辑

有人说它不安全

具体来说:Igor Ostrovsky在你引用的文章中,以及Stephen Toub在这篇博文的评论中的讨论内容:http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/ cooperatively-pausing-async-methods.aspx.

基本上他们说读取引入或消除是一种常见的编译器优化,如果C#和JIT不改变单线程行为,则允许这样做.

[注意:Eric Lippert已经说过C#编译器目前没有进行这样的优化.]

请注意,Igor似乎意识到JIT非常保守,并在文章中明确指出您的示例代码不会在x86-x64上的.NET 4.5中破坏.另一方面,他说在其他情况下它可能会中断,如果它是更复杂的代码模式,未来或过去的.net版本,或其他平台,则可能没有预先设定.

如果您想100%安全,解决方案是使用易失性读取.易失性读写被C#标准定义为副作用,因此无法引入或删除它们.

ECMA CLI标准有一个类似的明确声明,即不删除易失性读写.

关于线程安全事件的说明

正如许多人所指出的那样,线程安全性不仅仅是事件提升代码.您的事件处理程序应该在取消订阅后准备好进行调用.

我同意Hans Passant的最佳指导是"不要这样做",但有时你需要.在这些情况下,请确保您的事件处理程序代码也是线程安全的.在这些情况下,您可能还需要考虑更简单的基于锁的同步方法.