线程安全事件调用

触发事件时避免竞争条件(在multithreading应用程序中)的常见做法是:

EventHandler temp = SomeEvent; if (temp != null) temp(e); "Remember that delegates are immutable and this is why this technique works in theory. However, what a lot of developers don't realize is that this code could be optimized by the compiler to remove the local temp variable entirely. If this happens, this version of the code is identical to the first version, so a NullReferenceException is still possible." 

问题(根据该书)是“编译器可以优化此代码以完全删除本地临时变量。如果发生这种情况,此版本的代码与第一个版本相同,因此仍然可以使用NullReferenceException”

根据CLR通过C#,这是强制编译器复制事件指针的更好方法。

 virtual void OnNewMail(NewMailEventArgs e) { EventHandler temp = Interlocked.CompareExchange(ref NewMail, null, null); if (temp != null) temp(this, e); } 

这里,如果它为null,则CompareExchange将NewMail引用更改为null,如果它不为null,则不会更改NewMail。 换句话说,CompareExchange根本不会更改NewMail中的值,但它确实以primefaces,线程安全的方式返回NewMail中的值。 里希特,杰弗里(2010-02-12)。 CLR通过C#(第265页)。 OReilly Media – A. Kindle版。

我在.Net 4.0框架上,并不确定这可能如何工作,因为Interlocked.CompareExchange需要对位置的引用,而不是对事件的引用。

书中有错误,或者我误解了它。 有没有人实现过这种方法? 或者有一个更好的方法来防止这里的竞争条件?

UPDATE

这是我的错误,iterlocked代码工作。 我刚刚指定了错误的转换,但根据Bradley(下文),在.net 2.0及Windows上没有必要。

if/temp离开(在CLR 2.0及更高版本中), if/temp不允许编译器(或JIT)优化; CLR 2.0内存模型不允许引入堆读取(规则#2)。

因此, MyEvent无法再次读取; 必须在if语句中读取temp的值。

有关此情况的详细讨论,请参阅我的博客文章 ,并解释为什么标准模式没问题。

但是,如果您运行的是非Microsoft CLR(例如,单声道),但不提供CLR 2.0内存模型保证(但仅遵循ECMA内存模型),或者您在Itanium上运行(其中有一个众所周知的弱硬件内存模型),你需要像Richter这样的代码来消除潜在的竞争条件。

关于Interlocked.CompareExchange的问题,语法public event EventHandler NewMail只是C#语法糖,用于声明EventHandler类型的私有字段以及具有addremove方法的公共事件。 Interlocked.CompareExchange调用读取私有EventHandler字段的值,因此此代码的编译和工作正如Richter所描述的那样; 它在Microsoft CLR中是不必要的。

现在这只是对你的问题的部分答案,因为我不能评论使用Interlocked.CompareExchange,但我认为这些信息可能有用。

问题是编译器可以优化if / temp,

好吧,根据CLR通过C# (第264-265页)

[]代码可以由编译器优化以完全删除本地变量。 如果发生这种情况,此版本的代码与[引用事件两次的版本]相同,因此仍然可以使用NullReferenceException。

因此,有可能知道Microsoft的实时(JIT)编译器不会优化掉局部变量。 虽然这可能会改变,但不太可能,因为它可能会破坏很多应用程序。

这是因为.Net有一个强大的内存模型: http : //msdn.microsoft.com/en-us/magazine/cc163715.aspx#S5

不能引入读写操作。

但是,该模型不允许引入读取,因为这意味着从内存中重新获取值,而在低锁定代码中,内存可能会发生变化。

然而,Mono遵循一个更弱的内存模型 ,可以优化该局部变量。

一句话:除非你打算使用Mono,否则不用担心。

即便如此,使用volatile声明也可以抑制该行为。

我认为你错过了解释。 位置只是指向对象引用的指针[ msdn version:与comparand进行比较并可能被替换的目标对象。 ]。 以下代码在.NEt 4.0中正常工作

 public class publisher { public event EventHandler TestEvent; protected virtual void OnTestEvent(EventArgs e) { EventHandler temp = Interlocked.CompareExchange(ref TestEvent, null, null); if (temp != null) temp(this,e); } } 

我看看你产生的IL你会看到这个方法是这样调用的

 IL_000d: ldsflda class [mscorlib]System.EventHandler`1 ConsoleApplication1.Program::MyEvent IL_0012: ldnull IL_0013: ldnull IL_0014: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange>(!!0&,!!0,!!0) 

看到ldsflda – 我的事件是静态的,但它正在加载一个字段的地址。 该字段是编译器为每个事件生成的自动生成的委托字段。

该字段的定义如下:

 .field private static class [mscorlib]System.EventHandler`1 MyEvent