在事件调度之前检查null ...线程安全吗?

时间:2008-11-12 00:50:27

标签: c# multithreading events

让我感到困惑的东西,但从未引起任何问题...推荐的发送事件的方式如下:

public event EventHandler SomeEvent;
...
{
    ....
    if(SomeEvent!=null)SomeEvent();
}

在多线程环境中,此代码如何保证另一个线程不会在检查null和调用事件之间更改SomeEvent的调用列表?

6 个答案:

答案 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);
    //---------------------------
}

}

相关问题