« February 2008 | Main | April 2008 »
March 28, 2008
Degrees of Thread Safety
I'd like to talk about .NET events and thread safety, but I first need to clarify what we mean by “thread safety.” After all, the most important consideration as regards thread safety is how thread-safe your code has to be. If you can get away with being thread-hostile, you should do so. There's no sense in worrying about other threads in a single-threaded console application, for example.
If you are writing components, it is critically important that you document your level of thread-safety. Having a consistent vocabulary for describing thread safety thus becomes important.
Here is our preferred taxonomy for describing degrees of thread safety, adapted from this article, and from Joshua Bloch's Effective Java Programming Language Guide.
Immutable
Instances of this class appear constant to their clients; no external synchronization is necessary. Examples include String and Regex.
Most immutable objects need no internal synchronization; however, “lazy loading” will generally require some form of synchronization in order to maintain thread safety.
Thread-safe
Instances of this class are mutable, but all methods contain sufficient internal synchronization that instances may be used concurrently without the need for external synchronization. Examples include many of the System.Threading types, including ManualResetEvent and Timer.
Since most concurrent applications require more coarsely-grained synchronization than the per-method-call synchronization of a thread-safe class, and since thread-safety causes unnecessary overhead for single-threaded operations, truly thread-safe classes are often not needed.
Conditionally Thread-safe
Like thread-safe, except that the class (or an associated class) contains methods that must be invoked in sequence without interference from other threads. To eliminate the possibility of interference, the client must obtain an appropriate lock for the duration of the sequence.
The object returned by Hashtable.Synchronized, for example, is documented as such: “Synchronized supports multiple writing threads, provided that no threads are reading the Hashtable. The synchronized wrapper does not provide thread-safe access in the case of one or more readers and one or more writers.”
Thread-safe for multiple readers
Many classes are conditionally thread-safe if they are not being mutated. Examples include List<T> and Dictionary<TKey, TValue>.
Readers can only presume thread-safety on an instance of this type if they can be certain that there will be no writers on any thread. If there is any possibility of a writer, both readers and writers must use the same external synchronization.
It is easy to assume that a class is thread-safe for multiple readers, but unsynchronized “lazy loading” can easily cause a class to violate this degree of thread safety. Worse, a future “upgrade” of the class could introduce such a thing as an optimization. If a class is thread-safe for multiple readers, its documentation must indicate as such.
Thread-safe for a subset of operations
Some classes are mostly thread-compatible (see below), except for a few methods that are thread-safe. For example, the BeginInvoke method of Control can safely be called from any thread, even though Control otherwise has thread affinity.
Thread-safe except for Dispose
It can also be convenient to declare a class thread-safe except for the Dispose method. See Thread-safe disposable objects for details.
Freezable
A “freezable” object has a method (often named Freeze) that causes the object to become immutable and thus thread-safe. Until that point, the object is mutable and generally thread-compatible or even thread-hostile. Examples include classes that derive from the Freezable class of WPF, including Brush, Pen, and Transform.
Thread-compatible
Instances of this class can safely be used concurrently by surrounding each method invocation (and in some cases, each sequence of method invocations) by external synchronization. Most classes in the .NET Framework fall into this category and are documented as such: “Any public static members of this type are thread safe. Any instance members are not guaranteed to be thread safe.”
Thread-affined
A thread-affined class has thread affinity; that is, instances of that class may only be used from one thread, generally the thread with which the instance was created. Most user interface components (Windows Forms, WPF, etc.) have affinity to the “user interface thread” and can thus only be used from that thread. Methods of thread-affined classes should assert that they are being called on the proper thread.
Thread-hostile
This class is not safe for concurrent use by multiple threads, even if all method invocations are surrounded by external synchronizaton. By this definition, thread-affined classes are also thread-hostile. In general, the only classes in the .NET Framework that are “thread hostile” are those with thread affinity. Thread-hostile behavior also includes:
Modifying static data
Modifying static data that affect other threads is thread-hostile behavior, for example, setting the value of System.Console.OutputEncoding.
Posted by Ed Ball at 2:06 PM | Comments (0) | TrackBack
March 25, 2008
AllowDrop in WPF
When implementing a drag-and-drop target in WPF, don't forget to set AllowDrop to true! You wouldn't think it would be that hard to remember – after all, it usually doesn't work until you set it. However, I just tracked down a bug that resulted from AllowDrop not being set to true on a drag-and-drop target. In most circumstances, it was inheriting a true AllowDrop from an ancestor element, so it worked fine, but in other circumstances, one of its ancestors was setting AllowDrop to false for some reason. I really don't understand why, and I'm guessing there's a WPF bug hiding in there somewhere, but the fix was easy – always set AllowDrop to true if you want to handle the drag-and-drop target events (DragEnter, DragOver, Drop, etc.).
Incidentally, this was my first bug fix for which the .NET Framework Library Source Code proved invaluable. The ability to step through the code, set breakpoints, watch variables, etc. was key to tracking this one down.
Posted by Ed Ball at 1:26 PM | Comments (0) | TrackBack
March 14, 2008
EmptyIfNull
Another great use for the fact that C# extension methods work on null references is a method we call EmptyIfNull:
public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> seq)
{
return seq ?? Enumerable.Empty<T>();
}
This extension method simply returns the specified enumerable, unless it is null, in which case it returns an empty enumerable. It requires slightly less typing than using the null-coalescing operator.
For example, suppose the GetNames method normally returns a collection of strings, but could return null if there are none. Either of these statements will write each of the strings to the console:
(GetNames() ?? Enumerable.Empty<string>()).
ToList().ForEach(Console.WriteLine);
GetNames().EmptyIfNull().ToList().ForEach(Console.WriteLine);
I like the EmptyIfNull syntax better.
Posted by Ed Ball at 11:07 AM | Comments (0) | TrackBack
March 7, 2008
Assigning to C# events
When raising an event in C#, you first have to make sure it isn't null, because it will be null if there are no subscribers.
public event EventHandler Click;
public void RaiseClick()
{
// throws NullReferenceException if no subscribers
// Click(this, EventArgs.Empty);
// could throw NullReferenceException if unsubscribed on another thread
// if (Click != null)
// Click(this, EventArgs.Empty);
// never throws NullReferenceException
EventHandler handler = Click;
if (handler != null)
handler(this, EventArgs.Empty);
}
A few weeks ago, Andy Clymer demonstrated a fun way to avoid that boilerplate null-checking code by simply adding a do-nothing event handler.
public event EventHandler Click = delegate { };
public void RaiseClick()
{
// never throws NullReferenceException
Click(this, EventArgs.Empty);
}
Call me crazy, but somehow I didn't realize that I could assign to events! In fact, you can assign to events anywhere within the declaring class; you'll note that I do so twice in my previous post.
In the constructor, I use a similar pattern to always call a virtual method as the first event handler:
Disposing = delegate { OnDisposing(); };
And in the Dispose method, I assign the event to null to unsubscribe all of my subscribers.
Disposing = null;
I should mention that I'm not claiming that any of these patterns make events "thread safe" by any stretch; they only help to avoid throwing a NullReferenceException. In fact, I hope to talk more about the thread safety of events in a future post.
(You'll note that the name of my event-raising method starts with "Raise" rather than "On" -- I agree with Sebastien Lambla that raising events from OnXxx methods is an anti-pattern.)
Posted by Ed Ball at 8:49 AM | Comments (4) | TrackBack
March 5, 2008
Thread-safe disposable objects
Most disposable objects are not thread-safe. After all, calling Dispose on one thread while other threads are accessing the object is bound to cause problems.
It is possible to make a disposable object entirely thread-safe, of course, but you'd need to take a lock inside your Dispose method and inside every other property or method call that uses disposable state to prevent that state from being disposed while you're using it (or about to use it). Within that lock, you could safely check to see if your object is disposed and throw an ObjectDisposedException if it is.
To avoid the overhead of those locks, even our otherwise thread-safe objects have a disclaimer about the Dispose method: “This class is thread-safe except for Dispose, which is thread-compatible. Using an instance during or after its disposal results in unpredictable behavior.”
Whether a disposable object is entirely thread-safe or not, there are still obstacles when using disposable objects in multiple threads. Some disposable objects start background work as part of their job description; if the object is disposed while the background work is still running, that thread is likely to access disposed state and cause havoc. Similarly, clients of disposable objects may be doing work in background threads that use the object; if the client disposes the object while that background work is running, that thread is likely to access the object while it is being disposed, or after it is entirely disposed.
One solution to this problem is to catch ObjectDisposedException in your background work. If you get such an exception, you simply abandon the work on the assumption that it is no longer needed. Keep in mind that you'd have to make the disposable object entirely thread-safe for this to work, as described above; otherwise the object might be in the middle of a method call when it is disposed.
Our preferred solution is to cancel and wait for background work before disposing the object. (The mechanisms for canceling and waiting for background work are outside the scope of this post, though we may discuss it in a future post; needless to say, Thread.Abort is not a viable solution.)
In the case of the disposable object starting background work, it simply has to cancel and wait for background work as the first thing that it does in the Dispose method, before marking itself as being disposed or disposing any state.
In the case of the client with background work that is using the disposable object, the client should cancel and wait for that work before calling the Dispose method on that object.
There's another scenario that's somewhat common in our code base -- what if the client that is running the background work isn't the owner of the disposable object? How can the client cancel its background work before the object is disposed when it isn't responsible for calling Dispose on that object?
We solve this problem by implementing a Disposing event on the disposable object. The Disposing event is raised by the Dispose method of the disposable object before any actual disposing takes place. The client can cancel and wait for background work inside its event handler for the Disposing event.
public abstract class DisposableService : IDisposable
{
public event EventHandler Disposing;
public void Dispose()
{
if (Interlocked.Exchange(ref m_nDisposing, 1) != 0)
return;
Disposing(this, EventArgs.Empty);
Disposing = null;
m_bDisposed = true;
Dispose(true);
GC.SuppressFinalize(this);
}
protected DisposableService()
{
Disposing = delegate { OnDisposing(); };
}
protected void VerifyNotDisposed()
{
if (m_bDisposed)
throw new ObjectDisposedException(GetType().Name);
}
protected virtual void Dispose(bool bDisposing)
{
}
protected virtual void OnDisposing()
{
}
#if DEBUG
~DisposableService()
{
Debug.Fail("Not disposed: " + GetType().Name);
}
#endif
int m_nDisposing;
volatile bool m_bDisposed;
}
(In some cases where we have clients that depend on a disposable service, the client actually calls Dispose on itself in its handler for the Disposing event of that disposable service!)
All of this canceling and waiting inside Dispose methods and event handlers seems like a recipe for deadlock, but we haven't found a better solution to the problem of using disposable objects from multiple threads, and it has been working pretty well for us so far.
(For more on this subject, see Joe Duffy's recent post.)
Update: Inspired by Neil's comment, I've added a bit more thread safety to Dispose. Now, even if multiple threads call Dispose at the “same time,” the Disposing event won't be raised twice.
Posted by Ed Ball at 8:33 AM | Comments (6) | TrackBack