« Exception 0xc0020001 in C++/CLI assembly | Main | Events and Threads (Part 2) »

May 9, 2008

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 at May 9, 2008 10:25 AM

Trackback Pings

TrackBack URL for this entry:
http://blog.logos.com/mt-cgi/mt-tb.cgi/209

Comments

Have you read Programming .NET Components? As I recall, there's some interesting code in there surrounding event mechanisms. Also, I wrote a blog post that sparked some interesting debate on a topic related to this, not long ago. You can read it here: http://devlicio.us/blogs/rob_eisenberg/archive/2008/03/20/net-event-techniques.aspx

Posted by: Rob at May 9, 2008 1:18 PM

Yeah, the second edition of Programming .NET Components has lots of interesting event code. Reading that book helped me realize how complicated the topic is.

Posted by: Ed Ball at May 9, 2008 1:29 PM

I'm in the process of removing events from a key part of my system because it was just too hard to control them (in my case, the ordering of events was important). I had to step back and say, "OK, this is internal to my system and no one will know if I'm using events or not". In those cases, I'm going to start to think twice about heavily using events and prefer to use a more straightforward, explicit design.

Another problem I've had with events is memory sticking around longer than it needs to. You have a short-lived object (like a UI component) subscribing to a long-lived object (like an entity). Without explicitly unsubscribing from that event, the UI component stays around indefinately!

Posted by: Michael at May 13, 2008 3:01 AM

Yeah, memory leaks are a huge problem with events. It's easy to forget to unsubscribe, and sometimes there is no good time to unsubscribe. You can use weak references instead, but that's a pain, too.

Posted by: Ed Ball at May 13, 2008 7:18 AM

Post a comment




(you may use HTML tags for style)