Tuesday, August 05, 2008

An even better pattern to raise events

In a previous post, I explained a subtle bug in the way that events are raised in most code. This suble bug might cause a race condition that results in unexpected behavior when the code is executed in multiple threads.

You can read the details here.

From .NET 2 onwards, there is an even simple pattern to solve this problem:

   1: class Publisher
   2: {
   3:   public event EventHandler SomethingHappened = delegate { }; // cannot be null, important for thread safety
   5:   void Test()
   6:   {
   7:     SomethingHappened(this, new EventArgs());
   8:   }
   9: }

The trick is that the event is initialized to an empty delegate (this was not possible before .NET 2.0). When invoked, this empty delegate does nothing. However it guarantees that the event will never become null, so the race condition cannot occur.

So using this pattern it is always safe to raise the event and it is even not necessary anymore to check if the event is null.


jgauffin said...

How will garbage collection work with that? Doesn't the garbage collector think that the class is in used since the event has a listener?

Kristof said...

@jgauffin: this is no problem for the garbage collector.

A subscriber (the empty delegate in this case) will never keep the publisher alive. It is actually the other way around: it is the publisher who may keep the subscribers alive (this is a potential memory leak). I tried to explain this here.

In this case, if nobody has a reference to our object, both the object and the empty delegate will be collected.

jgauffin said...

Got it, thanks

Anonymous said...

Thanks Kristof, this is an excellent solution.

Anonymous said...

Thanks Kristof,

I Really like your blog and I find most articles very useful and well explained.

keep up the good work.

Владимир said...

There was a discussion about efficiency of "empty delegate" approach vs. conventional approach.

Instead of "empty delegate" approach one can define a simple extension method to encapsulate the conventional method of checking event handler against null. It is described here and here.