Threading through the open window of opportunity

February 7, 2007

It’s interesting that despite C# and the CLR making our lives easier by providing simpler code constructs and managing memory for us, it’s still very easy to screw things up; especially when it comes to thread safety and race conditions. In the good (bad) old days of C++, delete, _beginthread, etc you had to write more code to develop your multithreaded apps and particularly when developing multithreaded GUIs you had to take care and understand more what was actually happening. If you ended up with a race condition, deadlock or message pump block, it was easier (relatively) to see, hear and smell where the problem was under the hood.

By providing events, delegates and simple threading constructs, C# makes writing multi-threaded GUIs really rather straightforward. But it’s exactly those simple constructs that means developers do more with them and consequently find more devious ways to make things go wrong in seemingly non-deterministic ways.

Take events and delegates. The often quoted, thread unsafe example is:

public class EventPublisher
{
   public event EventHandler theEvent;
   public void InvokeEvent()
   {
      // If delegate has no targets it will be null
      if (theEvent != null)
      {
         // So it seems OK to use, but a thread context
         // switch may happen here and remove all the
         // invocation targets.
         theEvent(this, EventArgs.Empty); // Maybe boom!
      } 
   }
}

Clearly, there is a window of opportunity for it to go wrong. And if there’s an open window …..

One counter measure is to make a copy of the delegate. Delegates are immutable so by copying it to a temporary variable you keep a copy of the original state of the delegate, irrespective of any thread context switches. Making any change to the state of the delegate creates a new one on the heap and updates the reference to which the delegate point.

Remember, EventHandler is a shorthand declaration of both a delegate variable and an event of the same type at the same time, so when I’m talking delegates it’s an EventHandler in the code snippets.

But there’s still an open window for a race condition …

The JIT compiler may decide to optimize out your copying for performance reasons, so you are back where you started. Yet another published solution, again uses copying, but this time the copy happens by passing the delegate (EventHandler) as a method parameter.

public class EventPublisher
{
   public event EventHandler theEvent;
   public void InvokeEvent()
   {
      InvokeEvent(theEvent);
   }
   private void InvokeEvent(EventHandler handler)
   {
      if (handler != null)
      handler(this,EventArgs.Empty);
   }
}

On the face of it this looks to close the race condition window of opportunity, but surprisingly it is still open. The JIT compiler’s optimizations may still beat you and decide to inline your method. A method will be a candidate for inlining when its code is ‘small’; just a few lines of code (less than 32 bytes of IL). Exact details are not easy to find, but David Notario has written a couple of articles on JIT Optimizations that are worth a read; JIT optimiations I and JIT optimizations II

The answer, to be safe, is to prevent inlining for the method using the [MethodImpl(MethodImplOptions.NoInlining)] attribute.

public class EventPublisher
{
   public event EventHandler theEvent;
   public void InvokeEvent()
   {
      InvokeEvent(theEvent);
   }

[MethodImpl(MethodImplOptions.NoInlining)]
   private void InvokeEvent(EventHandler handler)
   {
     if (handler != null)
        handler(this,EventArgs.Empty);
   }
}

Hopefully the window is now tightly closed.

Advertisements

3 Responses to “Threading through the open window of opportunity”

  1. teknologikl Says:

    An alternative fix is to mark ‘theEvent’ as being ‘volatile’. This prevents the JIT from introducing a new read.

    –joe


  2. […] and multithreading. I can’t take credit for all of it; most of it has been covered already here, here, and […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: