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

时间:2022-11-16 11:46:21

Something that confuses me, but has never caused any problems... the recommended way to dispatch an event is as follows:

令我困惑的东西,但从来没有引起任何问题...推荐的事件发送方式如下:

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

In a multi-threaded environment, how does this code guarantee that another thread will not alter the invocation list of SomeEvent between the check for null and the invocation of the event?

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

6 个解决方案

#1


21  

In C# 6.0 you can use monadic Null-conditional operator ?. to check for null and raise events in easy and thread-safe way.

在C#6.0中,您可以使用monadic Null-conditional运算符吗?检查null并以简单和线程安全的方式引发事件。

SomeEvent?.Invoke(this, args);

It’s thread-safe because it evaluates the left-hand side only once, and keeps it in a temporary variable. You can read more here in part titled Null-conditional operators.

它是线程安全的,因为它只评估左侧一次,并将其保存在临时变量中。您可以在这里阅读更多标题为Null-conditional运算符的部分。

#2


54  

As you point out, where multiple threads can access SomeEvent simultaneously, one thread could check whether SomeEventis null and determine that it isn't. Just after doing so, another thread could remove the last registered delegate from SomeEvent. When the first thread attempts to raise SomeEvent, an exception will be thrown. A reasonable way to avoid this scenario is:

正如您所指出的,多个线程可以同时访问SomeEvent,一个线程可以检查SomeEventis是否为null并确定它不是。在这样做之后,另一个线程可以从SomeEvent中删除最后一个注册的委托。当第一个线程尝试引发SomeEvent时,将抛出异常。避免这种情况的合理方法是:

protected virtual void OnSomeEvent(EventArgs args) 
{
    EventHandler ev = SomeEvent;
    if (ev != null) ev(this, args);
}

This works because whenever a delegate is added to or removed from an event using the default implementations of the add and remove accessors, the Delegate.Combine and Delegate.Remove static methods are used. Each of these methods returns a new instance of a delegate, rather than modifying the one passed to it.

这是有效的,因为只要使用add和remove访问器的默认实现向事件添加或删除委托,就会使用Delegate.Combine和Delegate.Remove静态方法。这些方法中的每一个都返回一个新的委托实例,而不是修改传递给它的那个实例。

In addition, assignment of an object reference in .NET is atomic, and the default implementations of the add and remove event accessors are synchronised. So the code above succeeds by first copying the multicast delegate from the event to a temporary variable. Any changes to SomeEvent after this point will not affect the copy you've made and stored. Thus you can now safely test whether any delegates were registered and subsequently invoke them.

此外,在.NET中分配对象引用是原子的,并且同步添加和删除事件访问器的默认实现。因此,上面的代码首先将多播委托从事件复制到临时变量。在此之后对SomeEvent的任何更改都不会影响您制作和存储的副本。因此,您现在可以安全地测试是否已注册任何代理并随后调用它们。

Note that this solution solves one race problem, namely that of an event handler being null when it's invoked. It doesn't handle the problem where an event handler is defunct when it's invoked, or an event handler subscribes after the copy is taken.

请注意,此解决方案解决了一个争用问题,即在调用时事件处理程序为null。它不处理事件处理程序在调用时失效的问题,或者事件处理程序在复制后订阅的问题。

For example, if an event handler depends on state that's destroyed as soon as the handler is un-subscribed, then this solution might invoke code that cannot run properly. See Eric Lippert's excellent blog entry for more details. Also, see this * question and answers.

例如,如果事件处理程序依赖于处理程序未订阅时销毁的状态,则此解决方案可能会调用无法正常运行的代码。有关详细信息,请参阅Eric Lippert的优秀博客文章。另外,请参阅此*问题和答案。

EDIT: If you're using C# 6.0, then Krzysztof's answer looks like a good way to go.

编辑:如果您使用的是C#6.0,那么Krzysztof的答案看起来是一个很好的方法。

#3


21  

The simplest way remove this null check is to assign the eventhandler to an anonymous delegate. The penalty incurred in very little and relieves you of all the null checks, race conditions etc.

删除此null检查的最简单方法是将eventhandler分配给匿名委托。罚款很少,并使您免除所有空检,竞争条件等。

public event EventHandler SomeEvent = delegate {};

公共事件EventHandler SomeEvent = delegate {};

Related question: Is there a downside to adding an anonymous empty delegate on event declaration?

相关问题:在事件声明中添加匿名空委托是否有缺点?

#4


4  

The recommended way is a little different and uses a temporary as follows:

建议的方式稍有不同,使用临时如下:

EventHandler tmpEvent = SomeEvent;
if (tmpEvent != null)
{
    tmpEvent();
}

#5


3  

Safer approach:


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;
            }
        }

    }
}

-bill

#6


0  

I would like to suggest an slight improvment to RoadWarrior's answer by utilizing an extention function for the EventHandler:

我想通过利用EventHandler的扩展函数来建议对RoadWarrior的回答略有改进:

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);
        }                
    }
  }

With this extension in scope, events can be raised simply by:

通过扩展范围,可以简单地通过以下方式引发事件:

class SomeClass { public event EventHandler MyEvent;

class SomeClass {public event EventHandler MyEvent;

void SomeFunction()
{
    // code ...

    //---------------------------
    MyEvent.Raise(this);
    //---------------------------
}

}

#1


21  

In C# 6.0 you can use monadic Null-conditional operator ?. to check for null and raise events in easy and thread-safe way.

在C#6.0中,您可以使用monadic Null-conditional运算符吗?检查null并以简单和线程安全的方式引发事件。

SomeEvent?.Invoke(this, args);

It’s thread-safe because it evaluates the left-hand side only once, and keeps it in a temporary variable. You can read more here in part titled Null-conditional operators.

它是线程安全的,因为它只评估左侧一次,并将其保存在临时变量中。您可以在这里阅读更多标题为Null-conditional运算符的部分。

#2


54  

As you point out, where multiple threads can access SomeEvent simultaneously, one thread could check whether SomeEventis null and determine that it isn't. Just after doing so, another thread could remove the last registered delegate from SomeEvent. When the first thread attempts to raise SomeEvent, an exception will be thrown. A reasonable way to avoid this scenario is:

正如您所指出的,多个线程可以同时访问SomeEvent,一个线程可以检查SomeEventis是否为null并确定它不是。在这样做之后,另一个线程可以从SomeEvent中删除最后一个注册的委托。当第一个线程尝试引发SomeEvent时,将抛出异常。避免这种情况的合理方法是:

protected virtual void OnSomeEvent(EventArgs args) 
{
    EventHandler ev = SomeEvent;
    if (ev != null) ev(this, args);
}

This works because whenever a delegate is added to or removed from an event using the default implementations of the add and remove accessors, the Delegate.Combine and Delegate.Remove static methods are used. Each of these methods returns a new instance of a delegate, rather than modifying the one passed to it.

这是有效的,因为只要使用add和remove访问器的默认实现向事件添加或删除委托,就会使用Delegate.Combine和Delegate.Remove静态方法。这些方法中的每一个都返回一个新的委托实例,而不是修改传递给它的那个实例。

In addition, assignment of an object reference in .NET is atomic, and the default implementations of the add and remove event accessors are synchronised. So the code above succeeds by first copying the multicast delegate from the event to a temporary variable. Any changes to SomeEvent after this point will not affect the copy you've made and stored. Thus you can now safely test whether any delegates were registered and subsequently invoke them.

此外,在.NET中分配对象引用是原子的,并且同步添加和删除事件访问器的默认实现。因此,上面的代码首先将多播委托从事件复制到临时变量。在此之后对SomeEvent的任何更改都不会影响您制作和存储的副本。因此,您现在可以安全地测试是否已注册任何代理并随后调用它们。

Note that this solution solves one race problem, namely that of an event handler being null when it's invoked. It doesn't handle the problem where an event handler is defunct when it's invoked, or an event handler subscribes after the copy is taken.

请注意,此解决方案解决了一个争用问题,即在调用时事件处理程序为null。它不处理事件处理程序在调用时失效的问题,或者事件处理程序在复制后订阅的问题。

For example, if an event handler depends on state that's destroyed as soon as the handler is un-subscribed, then this solution might invoke code that cannot run properly. See Eric Lippert's excellent blog entry for more details. Also, see this * question and answers.

例如,如果事件处理程序依赖于处理程序未订阅时销毁的状态,则此解决方案可能会调用无法正常运行的代码。有关详细信息,请参阅Eric Lippert的优秀博客文章。另外,请参阅此*问题和答案。

EDIT: If you're using C# 6.0, then Krzysztof's answer looks like a good way to go.

编辑:如果您使用的是C#6.0,那么Krzysztof的答案看起来是一个很好的方法。

#3


21  

The simplest way remove this null check is to assign the eventhandler to an anonymous delegate. The penalty incurred in very little and relieves you of all the null checks, race conditions etc.

删除此null检查的最简单方法是将eventhandler分配给匿名委托。罚款很少,并使您免除所有空检,竞争条件等。

public event EventHandler SomeEvent = delegate {};

公共事件EventHandler SomeEvent = delegate {};

Related question: Is there a downside to adding an anonymous empty delegate on event declaration?

相关问题:在事件声明中添加匿名空委托是否有缺点?

#4


4  

The recommended way is a little different and uses a temporary as follows:

建议的方式稍有不同,使用临时如下:

EventHandler tmpEvent = SomeEvent;
if (tmpEvent != null)
{
    tmpEvent();
}

#5


3  

Safer approach:


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;
            }
        }

    }
}

-bill

#6


0  

I would like to suggest an slight improvment to RoadWarrior's answer by utilizing an extention function for the EventHandler:

我想通过利用EventHandler的扩展函数来建议对RoadWarrior的回答略有改进:

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);
        }                
    }
  }

With this extension in scope, events can be raised simply by:

通过扩展范围,可以简单地通过以下方式引发事件:

class SomeClass { public event EventHandler MyEvent;

class SomeClass {public event EventHandler MyEvent;

void SomeFunction()
{
    // code ...

    //---------------------------
    MyEvent.Raise(this);
    //---------------------------
}

}