Lately, I've been posting some code samples on how to do simple threading using Delegates as well as handling event registration manually. Nick pointed out the following post by Brad Adams that talks about a possible race condition when invoking an event (a)synchronously. Take for example, the following code:
class ClassA
{
public event EventHandler SomeEvent;
protected virtual void OnSomeEvent(object sender,EventArgs e)
{
if(SomeEvent != null)
SomeEvent(sender,e);
}
public void PerformAction()
{
OnSomeEvent(this,null);
}
}
The boolean check, if(SomeEvent != null), could lead to a race condition in the situation that another thread, which I will call BadThread, removes all the registered deletegates from the event. Here's a quick step-by-step layout:
- Main thread performs boolean check and evaluates to True
- Thread context switch occurs, BadThread removes all registered delegates and calls System.GC.Collect() to collect all unused objects and then terminates
- Thread context returns to Main thread where the call to the SomeEvent(sender,e) happens. You get a null pointer exception since SomeEvent is now null because of BadThread's actions.
Seems to make plenty of sense, right? The following solution, proposed by Jay Bazuzi, 'helps' aliviate the problem:
if(SomeEvent != null)
{
EventHandler h = SomeEvent;
h(sender,e);
}
This allows the application to have a local reference of the last EventHandler in the invocation list of SomeEvent. However, this doesn't solve the issue. Why? Cause there's still a chance that the local reference to the event is null due to step #2 at top. Since the GC.Collect() is called, this will clean the objects on the BadThread context, way before the local reference is assigned. Thus, the work around still has flaws.
Now, from here, we started looking around for some answers to this issue and this post by Roy Osherove had pretty good arguments to building a good work around for this.
It's interesting what one little method call can cause, isn't it?
Happy codin'!