在触发事件时,避免竞争条件(在多线程应用程序中)的一个常见做法是:
EventHandler<EventArgs> 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仍然是可能的。”
根据通过C#的CLR,这里有一种更好的方法来强制编译器复制事件指针。
virtual void OnNewMail(NewMailEventArgs e)
{
EventHandler<NewMailEventArgs> temp =
Interlocked.CompareExchange(ref NewMail, null, null);
if (temp != null)
temp(this, e);
}在这里,CompareExchange将NewMail引用更改为null,如果它为null,则不会更改NewMail。换句话说,CompareExchange根本不改变NewMail中的值,但它确实以原子的、线程安全的方式返回NewMail中的值。Richter,Jeffrey (2010-02-12)。CLR通过C# (第265页)。OReilly媒体- A. Kindle版。
我使用的是.Net 4.0框架,不确定它是如何工作的,因为Interlocked.CompareExchange需要对位置的引用,而不是对事件的引用。
要么书中有错误,要么我曲解了它。有人实现了这个方法吗?或者有更好的方法来防止这里的种族状况?
更新
这是我的错误,迭代锁代码起作用。我只是指定了错误的转换,但根据布拉德利(以下),它在.net 2.0和windows上的更新是不必要的。
发布于 2012-06-22 15:24:08
编译器(或JIT)不允许对if/temp进行优化(在CLR2.0及更高版本中);CLR2.0内存模型不允许从堆中读取(规则2)。
因此,不能第二次读取MyEvent;必须在if语句中读取temp的值。
有关这种情况的扩展讨论,请参阅我的博客文章,并解释为什么标准模式很好。
但是,如果您运行的非Microsoft CLR (例如,mono)不提供CLR2.0内存模型的保证(但只遵循ECMA内存模型),或者您运行的是Itanium (它的硬件内存模型非常薄弱),您将需要像Richter这样的代码来消除潜在的竞争状况。
关于Interlocked.CompareExchange的问题,语法public event EventHandler<NewMailEventArgs> NewMail只是声明EventHandler<NewMailEventArgs>类型的私有字段和具有add和remove方法的公共事件的C#语法糖。Interlocked.CompareExchange调用读取私有EventHandler<NewMailEventArgs>字段的值,因此这段代码按照Richter的描述进行编译和工作;它在Microsoft中是不必要的。
发布于 2012-06-22 15:43:55
现在,这只是对您的问题的部分回答,因为我不能评论使用Interlocked.CompareExchange,然而,我认为这些信息可能是有用的。
问题是编译器可能会优化如果/temp离开,
那么,根据通过C#的CLR (第264-265页)
编译器可以对代码进行优化以删除本地…。完全可变。如果发生这种情况,此版本的代码与两次引用事件的版本相同,因此仍然可以使用NullReferenceException。
因此,这是可能的,然而,重要的是要知道微软的JIT编译器并没有优化局部变量。虽然这可能会改变,但这是不可能的,因为它可能会破坏许多应用程序。
这是因为.Net有一个强大的内存模型:http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S5
不能引入读和写。
和
但是,该模型不允许引入读,因为这意味着从内存中重新获取值,而在低锁代码中,内存可能会发生变化。
但是,遵循弱得多的记忆模型的Mono可以对局部变量进行优化。
底线:除非你计划使用Mono,不要担心它。
即使如此,这种行为也可以通过不稳定的声明来抑制。
发布于 2012-06-22 15:39:20
我猜你可能错过了解说。位置意味着仅仅是一个指向对象引用msdn版本的指针:与比较和可能被替换的目标对象。下面的代码在.NEt 4.0中运行良好
public class publisher
{
public event EventHandler<EventArgs> TestEvent;
protected virtual void OnTestEvent(EventArgs e)
{
EventHandler<EventArgs> temp = Interlocked.CompareExchange(ref TestEvent, null, null);
if (temp != null)
temp(this,e);
}
}https://stackoverflow.com/questions/11159176
复制相似问题