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

我有与此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; } } 

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

 void PrintObj() { if (_obj != null) { Console.WriteLine(_obj.ToString()); } } 

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

 void RaiseEvent() { var myEvent = MyEvent; if (myEvent != null) { myEvent(this, EventArgs.Empty); } } 

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

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

有人说这种模式是安全的

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

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

“.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的最佳指导是“不要这样做”,但有时你需要。 在这些情况下,请确保您的事件处理程序代码也是线程安全的。 在这些情况下,您可能还需要考虑更简单的基于锁的同步方法。