As you point out, where multiple threads can access SomeEvent
simultaneously, one thread could check whether SomeEvent
is 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:
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.
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.
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.
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 StackOverflow question and answers.
EDIT: If you're using C# 6.0, then Krzysztof's answer looks like a good way to go.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…