Search
Close this search box.

C#/.NET Fundamentals: Safely and Efficiently Raising Events

The C#/.NET Fundamentals series is geared towards examining fundamental concepts in using C# (and .NET in general) to produce effective solutions. 

A couple of posts ago, I discussed the EventHandler<TEventArgs> and EventHandler delegates, and in particular at one point mentioned in a sidebar that you need to watch out for thread-safety in order to safely raise events in a multi-threaded environment.

There was an interesting discussion in the comments about different ways that people achieve their thread-safety when raising events, and that got me interested in exploring them more in detail and checking them for performance.

Introduction: The Problems with Raising Events

Raising events is pretty easy in the .NET Framework, but it does have its pitfalls.  Let’s illustrate with a quick example.  We’ll begin by assuming we are writing a messaging receiver, and every time a new message is received, we want to raise a MessageReceived event that contains the new message.

So, to follow the Microsoft recommended practices for events, we’ll create a set of event arguments that inherit from EventArgs (directly or indirectly), and those arguments will include the message we just received:

1 :  // all event arguments should inherit from EventArgs
     2 : public class MessageReceivedEventArgs : EventArgs 3 : {
  4 :  // gets the message we received
       5 : public string Message {
         get; private set;
       }
  6 : 7 :  // constructs an immutable ReceivedEventArgs with the message
           8 : public MessageReceivedEventArgs(string message) 9 : {
    10 : Message = message;
    11:
  }
  12:
}

And now, we’ll create our base UnsafeMessenger class which will implement this message and notify all subscribers of the event MessageReceived.  The OnNewMessage() method would typically be private and called by an internal process, but for the sake of performance testing we’re leaving it public to pump messages through:

1 :  // This is our base example, has no thread-safety
     2 : public class UnsafeMessenger
3 : {
  4 :  // the event listeners can subscribe to
       5 : public event EventHandler<MessageReceivedEventArgs> MessageReceived;
  6 : 7 :  // the method we'll call when we get a new message
           8 : public void OnNewMessage(string message) 9 : {
    10 : if (MessageReceived != null) 11 : {
      12 : MessageReceived(this, new MessageReceivedEventArgs(message));
      13:
    }
    14:
  }
  15:
}

On the surface, this seems fine and dandy!  And actually, if we were only using this in a single-threaded program, it would meet our needs.  The problem with this type of pattern for raising events, though, is that it is not thread-safe.

Why?  Keep in mind that a listener can subscribe or unsubscribe from an event at any given time.  Imagine that we have one thread that is processing new messages to be sent to the event listeners, and another thread (perhaps in the UI) that is subscribing and unsubscribing listeners.

Let’s say there’s one subscriber (currently) listening to this event.  If we get a new message and call OnNewMessage() to process it to all subscribers, we could end up hitting the if condition:

   1: if (MessageReceived != null)

Uh oh, now the MessageReceived delegate is null because it has no subscribers, this means that when the message processing thread hits the next line:

1 :  // We already passed the IF check
     2 : if (MessageReceived != null) 3 : {
  4 :  // NOW we are processing this line, but MessageReceived is now null!!!
       5 : MessageReceived(this, new MessageReceivedEventArgs(message));

We are now trying to invoke a null delegate, which will throw a NullReferenceException.  Thus, this pattern of raising an even works fine in single-threaded environments, but does not work well in multi-threaded environments.

What we need to do is find a way to make this thread-safe.  So, let’s begin to look at some ways to do this more correctly, and then we can measure them.

Lock it down! – Synchronizing the event using a lock…

The first potential solution many developers might jump to is to think that since we have a concurrency issue, we should just synchronize access to the event.  To do this, we would need to prevent users from subscribing or unsubscribing while we raise events and vice-versa.

At this point, a developer new to .NET may be scratching their head thinking, how do you synchronize access to an event?

1 :  // how do we guarantee lock access to this event while being invoked?
     2 : public event EventHandler<MessageReceivedEventArgs> MessageReceived;

After all, you can’t control event subscription and un-subscription to guarantee synchronization with raising the event, can you?  Well, yes, you can. 

It turns out declaring an event in .NET is much like declaring an auto-implemented property.  That is, declaring an event, using syntax like that of above, is short-hand for declaring a private delegate behind the scenes, and then exposing access for adding a subscription (add) and removing a subscription (remove).  Thus, the simple auto-event above is roughly identical to something like this:

1 :  // an auto-event is just a private delegate behind the scenes
     2 : private EventHandler<MessageReceivedEventArgs> _messageReceived;
3 : 4
    :  // what we declare as the event is who can access sub/un-sub of the event
       5 : public event EventHandler<MessageReceivedEventArgs> MessageReceived 6
    : {
  7 : add {
    _messageReceived += value;
  }
  8 : remove {
    _messageReceived -= value;
  }
  9:
}

Neat, huh?  So really, a public auto-event is really just a private delegate that allows you to subscribe and un-subscribe publically.  That is, the access specifier on the event only controls who can subscribe and unsubscribe to the event. This is the primary difference between a public event and a public delegate.  If a delegate is public than anyone can subscribe, unsubscribe, assign, and invoke the delegate.  Events, however, give you a handy way to only allow people to subscribe and unsubscribe, but deny them the ability to assign or invoke it the event directly.  This is typically what you want when dealing with events, your class handles invoking and assigning the event, and you only allow outside elements to subscribe or unsubscribe to it.

So, now that we know we can change the way add and remove work, we can synchronize access to them and to raising the event:

1 : public class SyncronizedMessenger : IMessenger 2 : {
  3 :  // delegate and lock
       4 : private EventHandler<MessageReceivedEventArgs> _messageReceived;
  5 : private readonly object _raiseLock = new object();
  6 : 7 :  // sub/unsub now in a lock
           8
      : public event EventHandler<MessageReceivedEventArgs> MessageReceived 9
      : {
    10 : add {
      lock (_raiseLock) {
        _messageReceived += value;
      }
    }
    11 : remove {
      lock (_raiseLock) {
        _messageReceived -= value;
      }
    }
    12:
  }
  13 : 14 :  // raise the event under the lock
             15 : public void OnNewMessage(string message) 16 : {
    17 : lock (_raiseLock) 18 : {
      19 : if (_messageReceived != null) 20 : {
        21 : _messageReceived(this, new MessageReceivedEventArgs(message));
        22:
      }
      23:
    }
    24:
  }
  25:
}

This way, if someone tries to subscribe/un-subscribe while an event is being raised, they’ll have to wait for the lock to be free first, and vice-versa.  This would, indeed, make us thread safe, but the down side is it could add a lot of contention, especially if subscriptions are changing fairly often and/or events are raised rapidly.

Regardless, it’s a lot of extra work to attempt to acquire a lock at every event trigger.  Thus, there are easier ways, but we started out with this one first since locking is generally the first resort people jump to in multi-threading.

Performing 1 billion iterations of raising an event using this method on my machine, and comparing it to the UnsafeMessenger, with 0-3 subscribers, we get (all times in milliseconds):

SubscribersUnsafeSynchronized
04,01131,005
18,60034,916
220,32645,089
323,92049,350

Notice that for zero subscribers, the SynchronizedMessenger is over 7 times slower, and for three subscribers it’s over twice as slow.  True, the UnsafeMessenger is still unsafe, whereas the SynchronizedMessenger is now thread-safe, but still this is heavier than we’d like to see.  So let’s look for a lighter way to achieve this goal.

Ignore It! – Attaching an empty subscriber

So let’s attack this from another way.  The main problem with the UnsafeMessenger is that it’s possible for a subscription to change between the null check and the invocation of the event.

Note: you do not need to worry about subscriptions changing during the invocation.  That is, using a delegate reference once is thread-safe because they are immutable (like strings).  It’s just the fact that we are using the delegate reference twice (once at null check, one at invocation) where we get into issues, since the reference could change between uses.

So, what if we just pre-loaded our event with a dummy delegate that does nothing!  Sounds simple enough in theory, but how does this stack up?

1 :  // a new subscriber that defaults the event to a single, empty listener
     2 : public class EmptySubscriberMessenger : IMessenger 3 : {
  4 :  // declare the event, and immediately assign it an empty listener
       5 : public event EventHandler<MessageReceivedEventArgs> MessageReceived =
               (s, e) => {};
  6 : 7 :  // now we can raise the event w/o checking for null!
           8 : public void OnNewMessage(string message) 9 : {
    10 : MessageReceived(this, new MessageReceivedEventArgs(message));
    11:
  }
  12:
}

Notice, that when we declare the event, we immediately attach a single, empty handler to it (just a simple lambda with an empty body).  This means that the delegate will never be null (unless we explicitly set it to null of course inside the class), and thus we never have to check for null. 

This eliminates the two uses of the delegate, so we never have to worry about the subscribers changing after we test but before we invoke.  So let’s run this one through 1 billion iterations as well and check out the results against our first two (all times in milliseconds):

SubscribersUnsafeSynchronizedEmptySubscriber
04,01131,0058,608
18,60034,91620,306
220,32645,08924,746
323,92049,35028,227

Notice that the EmptySubscriberMessenger is much more efficient than the SynchronizedMessenger, though still much heavier than the UnsafeMessenger for low numbers of subscribers.  One of the prime reasons for this, is that it must always invoke the delegate and create the event arguments even if no one (besides the empty handler) is subscribed to the event.  We can’t check to see if only the empty handler is attached, or we run into the same race condition as before in the UnsafeMessenger

Thus, even though this is an improvement, we can do better!

Copy that! – Creating a local copy of the delegate

There’s another simple thing we can do (and indeed this is the recommended thread-safe pattern): we can make a local copy of the delegate before testing and invoking.  This takes advantage of the fact that multi-cast delegates are immutable.  That is, every time you subscribe/un-subscribe from an event, it creates a new copy of the multi-cast delegate with the new subscription list (or null after last one unsubscribes). 

This is a lot like how immutable strings work, in that anything you do to manipulate a string leaves the old string intact, and returns a new string!  Consider this code:

1 :  // create a delegate
     2 : Action<string> myDelegate = null;
3 : 4 :  // adds one subscriber
         5 : myDelegate += Console.WriteLine;
6 : 7 :  // adds another subscriber
         8 : myDelegate += Console.Error.WriteLine;
9 : 10 :  // create a copy of the delegate
          11 : var copy = myDelegate;
12 : 13 :  // unsubscribe Console.WriteLine
           14 : myDelegate -= Console.WriteLine;
15 : 16 :  // This still goes to both Console and Console.Error because copy
           17
    :  // is a reference to a new multi-cast delegate cloned from myDelegate,
       18 :  // not simply a reference to myDelegate.
             19 : copy("Hi!");

If this is still confusing, think of how similar code would work if you were manipulating strings.  For example, when you append a string using +=, it returns a new string, and the original string is unaltered, thus any references to the original string don’t see the changes.  So we take myDelegate and add two subscribers to it, then we copy that delegate to copy.  Now copy and myDelegate both refer to the same multi-cast delegate instance.  At the point we say myDelegate –= Console.WriteLine though, this creates a new instance of a multi-cast delegate that only has the Console.Error.WriteLine in it and assigns it to myDelegate, but copy still refers to the original mutli-cast delegate with both!

So let’s see how we can apply this to make our messenger thread-safe:

1 :  // thread-safe messenger that takes advantage of immutability of multi-cast
     // delegates
     2 : public class LocalCopyMessenger : IMessenger 3 : {
  4 : public event EventHandler<MessageReceivedEventArgs> MessageReceived;
  5 : 6 :  // before we raise event, make local copy of delegate
           7 : public void OnNewMessage(string message) 8 : {
    9 : var target = MessageReceived;
    10 : 11 : if (target != null) 12 : {
      13 : target(this, new MessageReceivedEventArgs(message));
      14:
    }
    15:
  }
  16:
}

Looks nearly like the UnsafeMessenger except for the fact that we copy the delegate, and then use that copy to check for null and invoke!  This works because the assignment of the multi-cast delegate is thread-safe, and once assigned to target, any changes to MessageReceived will not affect target since they are now two separate delegate instances.  Thus, no changes to MessageReceived can invalidate or change target, and we are thread-safe!

So how does this stack up to the others?  Again we’ll run the new messenger through 1 billion iterations each for 0 to 3 subscribers, and we get:

SubscribersUnsafeSynchronizedEmptySubscriberLocalCopy
04,01131,0058,6084,178
18,60034,91620,3069,371
220,32645,08924,74620,663
323,92049,35028,22724,488

As you can see, the LocalCopyMessenger is the most efficient of all of the thread-safe messengers, and is very nearly as fast as the unsafe method! Graphing the different times really shows the differences of the methods and how close LocalCopyMessenger is to UnsafeMessenger:

image

Notice how the LocalCopyMessenger is the closest to the UnsafeMessenger in terms of performance for all numbers of subscribers tested.  In truth, these times are over 1 billion iterations, and the cost of invoking any one of these events once is negligible, but given that the local copy method is nearly as fast as un-safe code, and is extremely simple to implement, I’d make it my choice.

Sidebar: Adding an extension method helper

In one of the comments in a previous post on events, a reader had suggested making an extension method to extend EventHandler<TEventArgs> to trigger the event in order to make a bit of a cleaner abstraction instead of having to remember to make a local copy. 

For completeness, such an extension method might look like:

1 : public static class EventExtensions
2 : {
  3 :  // essentially, this does same thing as local copy method because the
       4 :  // parameter is a local variable.
            5 : public static void Raise<T>(this EventHandler<T> theEvent,
                                            object source, T args)
                    where T : EventArgs 6 : {
    7 :  // this is slightly less efficient than ordinary local copy because
         8 :  // of the method overhead, and you have to pass in an event args
              9 :  // regardless of whether a subscriber is attached or not.
                   10 : if (theEvent != null) 11 : {
      12 : theEvent(source, args);
      13:
    }
    14:
  }
  15:
}

Which would make our messenger look like:

1 :  // version of messenger that raises the event through a second method
     2 : public class ExtensionMethodMessenger : IMessenger 3 : {
  4 : public event EventHandler<MessageReceivedEventArgs> MessageReceived;
  5 : 6 :       // now invokes the Raise() extension method, which copies the
                // delegate reference on call
           7 :  // much like local copy, however we have to create our event
                // args regardless of whether
                8 :  // or not there are listeners, which could be a down-side.
                     9 : public void OnNewMessage(string message) 10 : {
    11 : MessageReceived.Raise(this, new MessageReceivedEventArgs(message));
    12:
  }
  13:
}

While this is a neat idea, and performs nearly identical to the local copy method, it does add an additional method call.  In addition, it would be a bit more difficult to find a clean way to figure out how to construct the event args to pass to the extension method only when there are actually subscribers.  If it is unlikely that you will ever have zero subscribers to the event, this may not be a major issue, but it’s worth noting to avoid generating garbage if it is possible for an event to have no subscribers.

Summary

here are many ways to raise events in a thread-safe manner.  Any of the methods listed here (except for the unsafe one, obviously) will serve you well, but the fastest and simplest approach is to create a local copy of the delegate before testing and invoking it.

For more information, I strongly recommend reading Eric Lippert’s excellent blog post on Events and Races.

https://web.archive.org/web/20111205034234if_/http://platform.twitter.com/widgets/tweet_button.html#_=1323056568293&count=horizontal&id=twitter-widget-0&lang=en&original_referer=http%3A%2F%2Fgeekswithblogs.net%2FBlackRabbitCoder%2Farchive%2F2011%2F12%2F01%2Fc.net-fundamentals-safely-and-efficiently-raising-events.aspx&text=C%23%2F.NET%20Fundamentals%3A%20Safely%20and%20Efficiently%20Raising%20Events&url=http%3A%2F%2Fgeekswithblogs.net%2FBlackRabbitCoder%2Farchive%2F2011%2F12%2F01%2Fc.net-fundamentals-safely-and-efficiently-raising-events.aspx&via=BlkRabbitCoderTechnorati Tags: C#CSharp.NETFundamentalseventsdelegatesmulti-threading

Print | posted on Thursday, December 01, 2011 8:10 PM | Filed Under [ My Blog C# Software .NET Fundamentals ]

This article is part of the GWB Archives. Original Author: James Michael Hare

Related Posts