Events and Threads

Tuesday, July 22nd, 2008

The purpose of this post is to provide not only a reference for events and multithreading, but also as a background for the fix to the ThreadBarrier pattern I previously introduced. The original ThreadBarrier implementation has a flaw (Problem #2 below). Rather than just posting the fix and not describing the problem in detail, I think it is better to provide an analysis and thorough investigation into the bug so that we all can learn together about the subtleties involved in multithreading. Remember: multithreading is hard. Tomorrow I will revisit the ThreadBarrier pattern with the fix as well as new ideas on how to use it. Until then, the following post shows several solutions to various problems with events and multithreading. I can’t take credit for all of it; most of it has been covered already here, here, and here.

Problem #1: Race condition while raising event

As long as your class is not intended to be thread-safe, it is best to implement events using the following well known pattern:

private event EventHandler MyEvent;

 

protected virtual void OnMyEvent(EventArgs e)

{

if (MyEvent != null)

{

MyEvent(this, e);

}

}

However, this pattern is not safe for use in multithreaded scenarios. The reason is because between the check for null and the calling of the handler, code executing on another thread may remove the handler. The check for null will pass, but when the call to the handler is made, it may then be null which causes a NullReferenceException. To illustrate how this can happen, consider the following code execution example.

UI Thread Worker Thread

protected virtual void OnMyEvent(EventArgs e)

{

if (MyEvent != null)

{

MyEvent -= new EventHandler(MyClass_MyEvent);

MyEvent(this, e);

}

}

Solution 1.A

One solution is to make a copy of the event delegate before performing the check for null:

protected virtual void OnMyEvent(EventArgs e)

{

EventHandler eventHandler = this.MyEvent;

if (eventHandler != null)

{

eventHandler(this, e);

}

}

Since delegates are immutable, when the same UI/Worker code execution sequence occurs, the eventHandler copy will never be affected no matter how or when handlers are added or removed from MyEvent:

UI Thread Worker Thread

protected virtual void OnMyEvent(EventArgs e)

{

EventHandler eventHandler = this.MyEvent;

if (eventHandler != null)

{

MyEvent -= new EventHandler(MyClass_MyEvent);

//eventHandler will never change even

//if handlers are removed to added to MyEvent

eventHandler(this, e);

}

}

Solution 1.B

According to various blog posts, there is a potential problem with solution 1.A. According to this post, Juval Lowy claims JIT compiler inlining may eliminate the copy and bring us back to the original problem. The solution is to raise the event in another method and use the attribute MethodImplOptions.NoInlining. Since passing the event to the method effectively makes a copy for us, we do not need to make another copy in the method.

protected virtual void OnMyEvent(EventArgs e)

{

RaiseEvent(this.MyEvent, e);

}

protected virtual void OnMyGenericEvent(MyEventArgs e)

{

RaiseEvent<MyEventArgs>(this.MyGenericEvent, e);

}

 

[MethodImpl(MethodImplOptions.NoInlining)]

private void RaiseEvent(EventHandler eventHandler, EventArgs e)

{

if (eventHandler != null)

{

eventHandler(this, e);

}

}

 

[MethodImpl(MethodImplOptions.NoInlining)]

private void RaiseEvent<T>(EventHandler<T> eventHandler, T e)

where T : EventArgs

{

if (eventHandler != null)

{

eventHandler(this, e);

}

}

If we are using .NET 3.5 or higher we can create a single extension method to do all of this work for us:

public static class EventExtensions

{

[MethodImpl(MethodImplOptions.NoInlining)]

public static void RaiseEvent(this EventHandler eventHandler, object sender, EventArgs e)

{

if (eventHandler != null)

{

eventHandler(sender, e);

}

}

 

[MethodImpl(MethodImplOptions.NoInlining)]

public static void RaiseEvent<T>(this EventHandler<T> eventHandler, object sender, T e)

where T : EventArgs

{

if (eventHandler != null)

{

eventHandler(sender, e);

}

}

}

Now raising events becomes very easy:

private event EventHandler MyEvent;

private event EventHandler<MyEventArgs> MyGenericEvent;

 

protected virtual void OnMyEvent(EventArgs e)

{

this.MyEvent.RaiseEvent(this, e);

}

protected virtual void OnMyGenericEvent(MyEventArgs e)

{

this.MyGenericEvent.RaiseEvent<MyEventArgs>(this, e);

}

Problem #2: Raising an event causes handler to execute in a disposed object

Now we are getting into the subtle areas of multithreading. This is an important scenario to understand because even though it may seem unlikely, it is actually quite easy to get bitten by this bug. To keep things simple, we will use the 1.A solution for the demonstration. The problem can occur when a listener on the UI thread removes its handler and is disposed, all before the worker thread calls the handler copy. This problem is not limited to a worker/UI thread scenario, it exists for any scenario where multiple threads are adding or removing event handlers.

SideNote: Event handlers are always executed on the thread that raised them, not the thread that added or removed the handler. For example, A UI control could start 10 worker threads where each worker adds an event handler. If the UI thread raises the event, all 10 handlers will execute on the UI thread.

To demonstrate problem #2, consider the following user control:

public partial class MyUserControl : UserControl

{

public event EventHandler MyEvent;

 

public MyUserControl()

{

InitializeComponent();

}

 

public void DemonstrateEventRaisedWhenDisposed()

{

this.MyEvent += new EventHandler(MyUserControl_MyEvent);

new Thread(() => OnMyEvent(EventArgs.Empty)).Start();

this.MyEvent -= new EventHandler(MyUserControl_MyEvent);

Dispose();

}

 

void MyUserControl_MyEvent(object sender, EventArgs e)

{

//do something with the UI here

}

 

protected virtual void OnMyEvent(EventArgs e)

{

EventHandler eventHandler = this.MyEvent;

if (eventHandler != null)

{

eventHandler(this, e);

}

}

}

If you are not familiar with lambda expressions, basically what is happening is that when DemonstrateEventRaisedWhenDisposed is called:

  1. An event handler is added to MyEvent
  2. A thread is started that will call OnMyEvent which raises MyEvent
  3. The previously added event handler from MyEvent is removed
  4. The control is disposed

Since there is no guarantee when the thread will start and execute, the code could execute in a safe way such as:

UI Thread Worker Thread

public void DemonstrateEventRaisedWhenDisposed()

{

this.MyEvent += new EventHandler(MyUserControl_MyEvent);

this.MyEvent -= new EventHandler(MyUserControl_MyEvent);

protected virtual void OnMyEvent(EventArgs e)

{

EventHandler eventHandler = this.MyEvent;

if (eventHandler != null)

{

eventHandler(this, e);

}

}

Dispose();

}

In this scenario, the event will not be raised by the worker thread, therefore the control will safely dispose and nothing bad will happen. But all it takes is some bad timing and a few context switches by the OS to expose the problem:

UI Thread Worker Thread

public void DemonstrateEventRaisedWhenDisposed()

{

this.MyEvent += new EventHandler(MyUserControl_MyEvent);

protected virtual void OnMyEvent(EventArgs e)

{

EventHandler eventHandler = this.MyEvent;

if (eventHandler != null)

{

this.MyEvent -= new EventHandler(MyUserControl_MyEvent);

Dispose();

}

eventHandler(this, e);

void MyUserControl_MyEvent(object sender, EventArgs e)

{

//two bad things: control is disposed, and we are

//executing on a worker thread in the control’s code

}

All it took was the worker to pass the null check with the delegate copy, the UI thread removing the original handler then disposing, and then the worker calling the handler that was never removed from the copy. The MyUserControl_MyEvent method will effectively be executing in the UI control code (yet still on the worker thread; a post to the UI thread is needed to update the UI) even though the control has been disposed.

Solution 2.A

Unfortunately if we do not put any restrictions on which thread handles the events, there is little we can do to solve the problem. But if we constrain our requirements such that events must only be handled on the UI thread, we are able to come up with a relatively simple solution. The solution is to always call the OnXXX method on the UI thread. If a worker needs to raise the event, the worker should post the OnXXX method to the UI thread. This will guarantee that the handlers will never be called when null. This requirement also allows us to remove the need for the copy since we specified that worker threads will not be listening for the event. Using an extension method and the UI’s SynchronizationContext (for posting to the UI thread) gives the simple solution:

public static void PostExt<T>(this SynchronizationContext synchronizationContext, T eventArgs, SendOrPostCallback func)

{

synchronizationContext.Post(func, eventArgs);

}

A call from a worker thread would use the UI’s synchronization context to post the OnXXXmethod to the UI thread for safely raising the event:

synchronizationContext.PostExt(EventArgs.Empty, e => OnMyEvent(EventArgs.Empty))

The synchronizationContext variable is the SynchronizationContext that was captured on the UI thread before the worker was started. For more information read the original ThreadBarrier article and stay tuned for tomorrow’s complete coverage and solution using this technique.

Solution 2.B

If our requirements are unconstrained and any object on any thread could be listening for events, we are in trouble. The only way to prevent the race condition is to lock around the event’s null check and handler call. However this is bad practice since it can lead to deadlocks (subject for another blog entry).

Do not use this code:

private event EventHandler MyEvent;

private object myEventObject = new object();

 

protected virtual void OnMyEvent(EventArgs e)

{

lock (myEventObject)

{

this.MyEvent.RaiseEvent(this, e);

}

}

Problem #3: Adding and removing of event handlers is not thread-safe by default

When a standard event is declared in a class and no Add/Remove accessors are explicitly defined, the C# 2.0+ compiler automatically make accesses to the event thread-safe. Consider the following standard event:

private event EventHandler MyEvent;

The compiler will automatically create thread-safe accessors:

[MethodImpl(MethodImplOptions.Synchronized)]

private void add_MyEvent(EventHandler value)

{

this.MyEvent = (EventHandler)Delegate.Combine(this.MyEvent, value);

}

[MethodImpl(MethodImplOptions.Synchronized)]

private void remove_MyEvent(EventHandler value)

{

this.MyEvent = (EventHandler)Delegate.Remove(this.MyEvent, value);

}

Unfortunately there is still a problem. When subscribing to the event in the following way:

 

MyEvent += new EventHandler(MyClass_MyEvent);

The compiler will convert this to:

MyEvent = (EventHandler)Delegate.Combine(MyEvent, new EventHandler(MyClass_MyEvent));

This makes the operation not thread-safe. It is almost as if the compiler only went half way to thread safety. If we were to start two threads running in parallel both adding and removing a handler (only one is shown below):

int numLoops = 10000000;

Thread thread1 = new Thread(() => {

for (int i = 0; i < numLoops; ++i)

{

this.MySafeEvent += new EventHandler(EventHandlerMethod);

this.MySafeEvent -= new EventHandler(EventHandlerMethod);

}

});

thread1.IsBackground = true;

thread1.Start();

If the adding and removing of the event were thread-safe, once both threads were complete the event should have zero handlers. Since the operation is not thread-safe, what happens is the event will have a few extra handlers due to races.

Solution 3.A

If multiple threads need to add and remove handlers, the event should be designed using the following pattern:

private object mySafeEventLock = new object();

private EventHandler mySafeEventHandler;

 

public event EventHandler MySafeEvent

{

add

{

lock (this.mySafeEventLock)

{

this.mySafeEventHandler += value;

}

}

remove

{

lock (this.mySafeEventLock)

{

this.mySafeEventHandler -= value;

}

}

}

The C# compiler will now make adding and removing handlers normal. This is code after compilation (identical to before compilation):

MySafeEvent += new EventHandler(this.EventHandlerMethod);

However, the addition and removal of the event is converted (again this is post compilation):

public event EventHandler MySafeEvent

{

add

{

lock (this.mySafeEventLock)

{

this.mySafeEventHandler = (EventHandler)Delegate.Combine(this.mySafeEventHandler, value);

}

}

remove

{

lock (this.mySafeEventLock)

{

this.mySafeEventHandler = (EventHandler)Delegate.Remove(this.mySafeEventHandler, value);

}

}

}

But since the handler update is happening inside the lock, the adds and removes are thread-safe. Click here for a sample that demonstrates both safe and unsafe event designs.

Conclusion

There are many mind bending scenarios involved when events are mixed with multithreading. It is best to try and keep things as simple as possible. Hopefully this post serves as a great reference for things to think about when designing your events, classes, and threads.

kick it on DotNetKicks.com