Why is the temporary variable preventing the client from removing the event handler?
The following piece of code is from the Effective C # book,
public event AddMessageEventHandler Log;
public void AddMsg ( int priority, string msg )
{
// This idiom discussed below.
AddMessageEventHandler l = Log;
if ( l != null )
l ( null, new LoggerEventArgs( priority, msg ) );
}
The AddMsg method shows the correct way to raise events. The temporary variable to reference the log event handler is an important protection against race conditions in multithreaded programs. Without a copy of the link, clients could remove event handlers between checking the if statement and executing the event handler. From copying a link that can't happen.
Why is the temporary variable preventing the client from removing the event handler? I am missing something here.
a source to share
Delegation chains are immutable. Therefore, if another thread accesses the "Log" and removes the event handler, a new delegate chain is assigned to the log. Therefore, on access l, even if the event handler is removed from the log, it will not affect l as it will no longer "point" to the same delegate chain. So yes, it protects against race conditions, however, you might run into a scenario where one thread is unsubscribed, but the evanthandler will still be called.
a source to share
This does not prevent the client from removing the event handler - it just means that you will call the event handler anyway.
An important bit that may be missing is that delegates are immutable - when the event handler is removed, the value Log
will change to a new delegate or null
. This is good because at this point you are using 1
instead Log
.
a source to share
This doesn't stop the client from removing the event handler - it just means that if they do, you won't call the null delegate ... consider:
- thread A: checks that
Log
it is not null - stream B: unsubscribes, which causes it to
Log
become null. - thread A: calls
Log
(which is now null) = arrow
Of course, with the above fix, phantom is now called and instead - that is, the subscriber can trigger an event triggered after they have unsubscribed ... gotta love threading.
As always, Eric Lippert has a blog on the topic: Events and Races
a source to share
The client can still remove the event handler.
Bad:
if ( Log != null )
{
//another thread removes the event handler at this point,
// Log is now null
Log ( null, new LoggerEventArgs( priority, msg ) );
}
Okay:
AddMessageEventHandler l = Log;
if ( l != null )
//another thread removes the event handler at this point,
// Log is now null
// l is not null, so we are safe.
l ( null, new LoggerEventArgs( priority, msg ) );
a source to share
Because when you assign it, you create a local reference to the object, then check if the reference exists before proceeding.
If you just noted that the Log was not empty and then proceeded to the next statement, another thread could zero out the Log object between the two statements.
If(Log != null)
// another thread could null the reference to Log here
l.DoSomething()
AddMessageEventHandler l = Log;
if ( l != null )
// nothing can null the reference here as you’ve created a local copy of that reference
l ( null, new LoggerEventArgs( priority, msg ) );
}
a source to share
Bold text explains what could go wrong.
Without a copy of the link, clients can remove event handlers between checking the if statement and executing the event handler.
The second thread can remove the event handler from the log while your AddMsg method is running. For instance:
if (Log != null)
{
// other thread has just removed its event handler, so Log is now null
Log(null, new LoggerEventArgs(priority, msg)); // Oops! Throws NullReferenceException
}
a source to share