MSDN杂志中的一篇文章讨论了Read Introduction的概念,并给出了一个可以被它破坏的代码示例.
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)
注意这个"可能抛出NullReferenceException"的注释 - 我从来不知道这是可能的.
所以我的问题是:我如何防止阅读介绍?
我还非常感谢编译器决定引入读取的确切解释,因为该文章不包括它.
我担心看似标准的前C#6模式的正确性可以解雇事件:
EventHandler localCopy = SomeEvent;
if (localCopy != null)
localCopy(this, args);
Run Code Online (Sandbox Code Playgroud)
我已经阅读了Eric Lippert的事件和比赛,并且知道调用过时的事件处理程序还有一个问题,但我担心的是,是否允许编译器/ JITter优化掉本地副本,有效地将代码重写为
if (SomeEvent != null)
SomeEvent(this, args);
Run Code Online (Sandbox Code Playgroud)
有可能NullReferenceException.
根据C#语言规范,§3.10,
必须保留这些副作用的顺序的关键执行点是对volatile字段(第10.5.3节),锁语句(第8.12节)以及线程创建和终止的引用.
- 所以在上述模式中没有关键执行点,优化器也不受此限制.
由于条件的原因,JIT不允许在第一部分中执行您正在讨论的优化.我知道这是作为一个幽灵提出的,但它无效.(我刚才和Joe Duffy或Vance Morrison一起检查过;我不记得是哪一个.)
- 但是评论引用了这篇博客文章(2008年):事件和线程(第4部分),它基本上说CLR 2.0的JITter(可能是后续版本?)不能引入读取或写入,所以一定没有问题在Microsoft .NET下.但这似乎与其他.NET实现没有任何关系.
[旁注:我没有看到不引入读取证明了所述模式的正确性.难道JITter只是看到一些SomeEvent其他局部变量的陈旧值并优化其中一个读取,而不是另一个?完全合法,对吗?]
此外,这篇MSDN文章(2012年):Igor Ostrovsky的理论与实践中的C#记忆模型陈述如下:
非重新排序优化某些编译器优化可能会引入或消除某些内存操作.例如,编译器可能用一次读取替换字段的重复读取.类似地,如果代码读取字段并将值存储在局部变量中然后重复读取变量,则编译器可以选择重复读取该字段.
因为ECMA C#规范不排除非重新排序的优化,所以它们可能是允许的.实际上,正如我将在第2部分中讨论的那样,JIT编译器确实执行了这些类型的优化.
这似乎与Jon Skeet的答案相矛盾.
由于现在C#不再是Windows语言,因此问题在于,模式的有效性是否是当前CLR实现中有限的JITter优化的结果,或者是语言的预期属性.
所以,问题是:从C#-the-language的角度来看,正在讨论的模式是否有效?(这意味着是否需要语言编译器/运行时来禁止某种优化.)
当然,欢迎就该主题提出规范性参考.
从 .NET 4.0 开始,自动生成的添加/删除事件处理程序是线程安全的(此处和此处)。因此,将其侦听器注册到公开事件的客户端可以从多个线程同时进行,而不会发生竞争。
但是如果我想以线程安全的方式触发事件怎么办?推荐的做法似乎如下(这里):
public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
EventHandler myEvent = MyEvent;
if (myEvent != null)
{
myEvent(this, e);
}
}
Run Code Online (Sandbox Code Playgroud)
但是,在阅读了有关 .NET 内存模型的内容后(例如 MSDN 杂志2012-12和2013-01),我不再认为这是正确的。我担心的是编译器可能会引入内存读取,因此上面的代码可能会被 JIT-ted 变成这样:
public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
// JIT removed the local variable and introduced two memory reads instead.
if (MyEvent != null)
{
// A race condition may cause the following line to …Run Code Online (Sandbox Code Playgroud)