Events and Threads (Part 1)

Once upon a time, I mentioned that I’d like to blog about thread-safety as it relates to events, so I figured I’d better get moving on that.

There are so many issues with .NET events and threads that it’s hard to know where to begin, but let’s start with the adding and removing of event handlers.

Unless documentation specifies otherwise, one must assume that adding and removing an event handler falls under the same thread safety requirements as any other method of the class. So, if the class has thread affinity (Windows Forms controls, WPF elements, etc.), assume that events can only be added and removed from the UI thread. If the class is thread-compatible (most non-UI classes in .NET), assume that events can be added and removed from any thread, but no two threads can add or remove events (or call any other method, for that matter) at the same time.

When authoring an event, if you allow C# to implement the add and remove methods (by not including your own), the default implementation attempts to be thread-safe by locking “this” before adding or removing the handler from the event delegate. In other words, these two events are implemented the same way:

    public event EventHandler Event1;
    public event EventHandler Event2
        add { lock (this) m_event2 += value; }
        remove { lock (this) m_event2 -= value; }
    private EventHandler m_event2;

If your event has thread affinity or is thread-compatible, the lock is unnecessary overhead, so you’re better off with a lock-free implementation:

    public event EventHandler Event3
        add { m_event3 += value; }
        remove { m_event3 -= value; }
    private EventHandler m_event3;

Better yet, if your event has thread affinity, make sure that the caller is on the UI thread.

    public event EventHandler Event4
        add { VerifyAccess(); m_event4 += value; }
        remove { VerifyAccess(); m_event4 -= value; }
    private EventHandler m_event4;

Furthermore, locking “this” is not recommended (see the MSDN documentation on the lock statement and on MethodImplOptions.Synchronized), so you might consider always implementing your own add and remove methods anyway.

While we’re on the subject of adding and removing event handlers, if your class has more than a few events, consider using the EventHandlerList class to manage all of the event handlers, or manage the event handlers in a similar way with your own collection. This will save memory when many of the events have no subscribers. The EventHandlerList class is not thread-safe, which makes it most suitable for thread-affined and thread-compatible events.

There’s obviously much more to discuss, not the least of which is a discussion of what it would mean for an event to be entirely thread-safe; hopefully part 2 won’t be so long in coming!

Posted by Ed Ball on May 09, 2008