有些事情让我感到困惑,但从来没有造成任何问题......推荐的调度事件的方式如下:
public event EventHandler SomeEvent;
...
{
....
if(SomeEvent!=null)SomeEvent();
}
在多线程环境中,此代码如何保证另一个线程不会在检查 null 和调用事件之间更改 SomeEvent
的调用列表?
最佳答案
正如您所指出的,在多个线程可以同时访问 SomeEvent
的情况下,一个线程可以检查 SomeEvent
是否为 null 并确定它是否为 null。就在这样做之后,另一个线程可以从 SomeEvent
中删除最后注册的委托(delegate)。当第一个线程尝试引发 SomeEvent
时,将抛出异常。避免这种情况的合理方法是:
protected virtual void OnSomeEvent(EventArgs args)
{
EventHandler ev = SomeEvent;
if (ev != null) ev(this, args);
}
这是有效的,因为每当使用添加和删除访问器的默认实现将委托(delegate)添加到事件或从事件中删除时,就会使用 Delegate.Combine 和 Delegate.Remove 静态方法。这些方法中的每一个都返回一个委托(delegate)的新实例,而不是修改传递给它的委托(delegate)实例。
此外,.NET 中对象引用的赋值是 atomic , 添加和删除事件访问器的默认实现是 synchronised .所以上面的代码通过首先将多播委托(delegate)从事件复制到一个临时变量而成功。在此之后对 SomeEvent 的任何更改都不会影响您制作和存储的副本。因此,您现在可以安全地测试是否有任何委托(delegate)已注册并随后调用它们。
请注意,此解决方案解决了一个竞争问题,即事件处理程序在被调用时为 null 的问题。它不处理事件处理程序在调用时失效的问题,或者事件处理程序在获取副本后订阅的问题。
例如,如果事件处理程序所依赖的状态在处理程序取消订阅后立即销毁,那么此解决方案可能会调用无法正常运行的代码。参见 Eric Lippert's excellent blog entry更多细节。另请参阅 this StackOverflow question and answers .
编辑:如果您使用的是 C# 6.0,则 Krzysztof's answer看起来是个不错的选择。
关于c# - 在事件分派(dispatch)之前检查 null ...线程安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/282653/