【问题标题】:Event raising and Read introduction in .net 4.5.net 4.5 中的事件引发和阅读介绍
【发布时间】:2013-04-23 06:49:18
【问题描述】:

我有关于此 MSDN 杂志article 的问题。

阅读简介正如我刚刚解释的那样,编译器有时会熔断 多读合二为一。编译器还可以拆分单个读取 成多读。在 .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);
    }
}

我在这里错过了什么重要的事情吗?

【问题讨论】:

  • this question 涵盖了类似的基础,指出在正常使用中,您不太可能真正遇到问题(因为 JIT 团队知道这种模式,他们不太可能引入优化并打破它),还有一个answer 显示了一种防止它发生的方法。
  • 这些是可怕的东西,会在晚上发生碰撞。该代码的非优化版本也不是没有问题的。您仍在触发侦听器可能已经取消订阅的事件。这也不会经常有好的结果。最好的指导很简单:不要这样做。

标签: c# .net multithreading events


【解决方案1】:

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

1。有人说这种模式是安全的

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

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

“.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,以及本博文 cmets 内部讨论中的 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 的观点,即最好的指导是“不要这样做”,但有时您需要这样做。在这些情况下,只需确保您的事件处理程序代码也是线程安全的。在这些情况下,您可能还需要考虑一种更简单的基于锁的同步方法。

【讨论】:

  • 为了解决您关于 ECMA CLI 规范和 C# 规范的观点:CLR 2.0 做出的更强大的内存模型承诺是 Microsoft 做出的承诺。决定自己实现 C# 并生成在他们自己的 CLI 实现上运行的代码的第三方可以选择较弱的内存模型,并且仍然符合规范。 Mono 团队是否这样做,我不知道;你得问问他们。
  • 感谢 Eric 的精确!因为这似乎没有记录(至少“官方”),所以它引出了一个问题:它是一个可靠的承诺吗? IE。如果我只针对 MS .net,我可以假设在任何平台和任何未来版本上都不会发生读写介绍吗?
  • 嘿,即使我确实为 Microsoft 工作,我也无法做出这样的承诺!我听说过这样的谣言,即在 Surface 设备等弱内存模型硬件上运行的 CLR 确实会生成强制执行更强内存模型的代码,但谁知道随着硬件的不断发展,未来会发生什么?您应该向真正有能力明确回答的人提出您的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-18
  • 2010-12-08
  • 2011-01-17
相关资源
最近更新 更多