让我感到困惑的东西,但从未引起任何问题...推荐的发送事件的方式如下:
public event EventHandler SomeEvent;
...
{
....
if(SomeEvent!=null)SomeEvent();
}
在多线程环境中,此代码如何保证另一个线程不会在检查null和调用事件之间更改SomeEvent
的调用列表?
答案 0 :(得分:55)
正如您所指出的,在多个线程可以同时访问SomeEvent
的情况下,一个线程可以检查SomeEvent
是否为空并确定它不是。在这样做之后,另一个线程可以从SomeEvent
中删除最后一个注册的委托。当第一个线程尝试引发SomeEvent
时,将抛出异常。避免这种情况的合理方法是:
protected virtual void OnSomeEvent(EventArgs args)
{
EventHandler ev = SomeEvent;
if (ev != null) ev(this, args);
}
这是有效的,因为只要使用add和remove访问器的默认实现向事件添加或删除委托,就会使用Delegate.Combine和Delegate.Remove静态方法。这些方法中的每一个都返回一个新的委托实例,而不是修改传递给它的那个实例。
此外,在.NET中分配对象引用是atomic,添加和删除事件访问器的默认实现是synchronised。因此,上面的代码首先将多播委托从事件复制到临时变量。在此之后对SomeEvent的任何更改都不会影响您制作和存储的副本。因此,您现在可以安全地测试是否已注册任何代理并随后调用它们。
请注意,此解决方案解决了一个争用问题,即在调用时事件处理程序为null。它不处理事件处理程序在调用时失效的问题,或者事件处理程序在复制后订阅的问题。
例如,如果事件处理程序依赖于处理程序未订阅时销毁的状态,则此解决方案可能会调用无法正常运行的代码。有关详细信息,请参阅Eric Lippert's excellent blog entry。另请参阅this StackOverflow question and answers。
编辑:如果您使用的是C#6.0,那么Krzysztof's answer似乎是一个不错的选择。
答案 1 :(得分:23)
在C#6.0中,您可以使用monadic Null条件运算符?.
来检查null并以简单且线程安全的方式引发事件。
SomeEvent?.Invoke(this, args);
它是线程安全的,因为它仅评估左侧一次,并将其保存在临时变量中。您可以在标题为Null-conditional operators的部分中阅读更多here。
答案 2 :(得分:22)
删除此null检查的最简单方法是将eventhandler分配给匿名委托。罚款很少,并使您免除所有空检,竞争条件等。
public event EventHandler SomeEvent = delegate {};
相关问题:Is there a downside to adding an anonymous empty delegate on event declaration?
答案 3 :(得分:4)
建议的方式略有不同,使用临时方法如下:
EventHandler tmpEvent = SomeEvent;
if (tmpEvent != null)
{
tmpEvent();
}
答案 4 :(得分:3)
更安全的方法:
public class Test
{
private EventHandler myEvent;
private object eventLock = new object();
private void OnMyEvent()
{
EventHandler handler;
lock(this.eventLock)
{
handler = this.myEvent;
}
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
public event MyEvent
{
add
{
lock(this.eventLock)
{
this.myEvent += value;
}
}
remove
{
lock(this.eventLock)
{
this.myEvent -= value;
}
}
}
}
- 比尔
答案 5 :(得分:0)
我想建议对RoadWarrior的回答略有改进 利用EventHandler的扩展函数:
public static class Extensions
{
public static void Raise(this EventHandler e, object sender, EventArgs args = null)
{
var e1 = e;
if (e1 != null)
{
if (args == null)
args = new EventArgs();
e1(sender, args);
}
}
}
在范围内使用此扩展名,可以简单地通过以下方式引发事件:
类SomeClass { 公共事件EventHandler MyEvent;
void SomeFunction()
{
// code ...
//---------------------------
MyEvent.Raise(this);
//---------------------------
}
}