Thursday, July 13, 2006

Safely firing events in C# without locking

Long time no see…
I’ve been busy with plenty of stuff, and I don’t think this will change much in the next coming months – I’m trying to finish my thesis. So basically I put my blogging into (very) deep freezing state. I don’t promise it won’t remain that way until I finish with my thesis…
Anyway, yesterday I went to hear Juval Lowy at the C++/VB Users Group meeting. As usual, his talk was excellent and very informative. Also as usual, I learned as much from things he said on the side as I learned from the actual presentation. One of these side-things I learned really stunned me. It’s the simplicity in itself, but I never saw anything written on this, and even when I tried searching a bit about this on the Internet, I couldn’t find any concrete discussion.
Problem definition: How to properly handle events in a multi-threaded environment?
Just to get in sync, look at the following simple event-handling code:
public delegate void DummyEvent();
public class EventDemo
{

private event DummyEvent dummyEventRace;

public virtual event DummyEvent DummyEventRace
{
add
{
if (dummyEventRace != null)
dummyEventRace += value;
}
remove
{
if (dummyEventRace != null)
dummyEventRace -= value;
}
}

protected virtual void onDummyEventRace()
{
if (dummyEventRace != null)
dummyEventRace();
}
}

In a single-threaded world, this works fine. I make sure every access to the event first checks that it’s not null, and thus avoid the very annoying exception, and life is pretty cool.
However, in a multi-threaded environment, you could easily get into a race condition:
Thread A wants to fire an event and calls onDummyEventRace() . I manages to check whether dummyEventRace != null and then comes a context-switch.
Thread B gets the context and tries to remove an event handler – it fully removes it from dummyEventRace and since it’s the last handler, dummyEventRace becomes null .
Thread A gets the context again and – Ooops – exception!!!

So, to avoid this kind of problems, what most people would do (me inclusive) was to add a synchronization mechanism, that ensures each of the operations with dummyEventRace would be done atomically. It would look something like this:

public delegate void DummyEvent();
public class EventDemo
{

private object eventLocker = new object();
private event DummyEvent dummyEventLock;

public virtual event DummyEvent DummyEventLock
{
add
{
lock (eventLocker)
{
if (dummyEventLock != null)
dummyEventLock += value;
}
}
remove
{
lock (eventLocker)
{
if (dummyEventLock != null)
dummyEventLock -= value;
}
}
}

public virtual void onDummyEventLock()
{
if (dummyEventLock != null)
dummyEventLock();
}
}
This solution solves the race condition issue completely, but introduces a potential dead-lock issue. If the handler of the event must get hold of another resource inside its event handling method, and the same resource is being locked by the thread that tries to remove the event… In other words – it works, but could get nasty.
Anyway, until yesterday, not only did I think that was the proper way to do it – I thought it was the only way to do it.
I was WRONG!!!

Take a look at this:
public delegate void DummyEvent();
public class EventDemo
{

private event DummyEvent dummyEventNoLock = delegate {};
public virtual event DummyEvent DummyEventNoLock
{
add
{
dummyEventNoLock += value;
}
remove
{
dummyEventLock -= value;
}
}

public virtual void onDummyEventNoLock()
{
dummyEventNoLock();
}
}
Amazing, isn’t it? The dummyEventLock never turns back to null so there is no reason to check. Thus, no need to lock and you’re rid of both the race condition problem and the dead lock risk!!!
How come this is nowhere to be found in the MSDN documentation (to my knowledge) ???

1 comment:

Oren Ellenbogen said...

I've posted about this little pearl myself:
http://www.lnbogen.com/SafeEventsWithNet.aspx

So simple and yet so powerful.