2

我有与这篇 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 值在 obj.ToString 表达式中永远不会为空。但是,这行代码实际上可能会抛出 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);
    }
}

我在这里错过了一些重要的事情吗?

4

1 回答 1

5

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

1.有人说模式是安全的

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

“不能引入读写”,MSDN 杂志(10 月 5 日),文章了解 Low-Lock 技术在多线程应用程序中的影响

“.NET 内存模型禁止 [阅读介绍] 用于引用 GC 堆内存的普通变量”,Joe Duffy,《Windows 上的并发编程》一书,pp517-8。

[注意:Joe Duffy 基本上说了同样的话,但留下了在堆栈上阅读介绍的可能性,这不是共享的,因此是安全的]

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

另一方面,我相信 JIT 团队知道这些模式并且不会破坏它们,至少在 x86 上......但是说这与说它在标准中是不同的。团队决定可能会在未来或在其他平台上发生变化。

编辑不要错过下面 Eric Lippert 的评论:“没有阅读介绍”是Microsoft CLI 实施的承诺。在 ECMA 标准中没有任何关于它的内容,当使用其他实现(例如 Mono)时,所有的赌注都没有了。结束编辑

2. 有人说不安全

具体来说:Igor Ostrovsky 在您引用的文章中,以及 Stephen Toub 在此博客文章评论中的讨论:http: //blogs.msdn.com/b/pfxteam/archive/2013/01/13/合作暂停异步方法.aspx

基本上,他们说读取介绍或消除是一种常见的编译器优化,如果 C# 和 JIT 不改变单线程行为,它们都可以这样做。

[注意:Eric Lippert 说 C# 编译器目前没有进行此类优化。]

请注意,Igor 似乎意识到 JIT 相当保守,并在文章中明确指出您的示例代码不会在 x86-x64 上的 .NET 4.5 中中断。另一方面,他说它可能会在其他情况下中断,而没有精确说明它是更复杂的代码模式、未来或过去的 .net 版本还是其他平台。

解决方案

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

ECMA CLI 标准有一个类似的关于不删除易失性读写的明确声明。

关于线程安全事件的说明

正如许多人所指出的,线程安全不仅仅是事件引发代码。您的事件处理程序应该可以在取消订阅后被调用。

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

于 2013-05-28T10:26:49.093 回答