Why is there no generic synchronized queue in .NET?

skb picture skb · Sep 20, 2008 · Viewed 13.3k times · Source

I noticed that you can call Queue.Synchronize to get a thread-safe queue object, but the same method isn't available on Queue<T>. Does anyone know why? Seems kind of weird.

Answer

Matt Ryan picture Matt Ryan · Sep 20, 2008

Update - in .NET 4, there now is ConcurrentQueue<T> in System.Collections.Concurrent, as documented here http://msdn.microsoft.com/en-us/library/dd267265.aspx. It's interesting to note that its IsSynchronized method (rightly) returns false.

ConcurrentQueue<T> is a complete ground up rewrite, creating copies of the queue to enumerate, and using advanced no-lock techniques like Interlocked.CompareExchange() and Thread.SpinWait().

The rest of this answer is still relevant insofar as it relates to the demise of the old Synchronize() and SyncRoot members, and why they didn't work very well from an API perspective.


As per Zooba's comment, the BCL team decided that too many developers were misunderstanding the purpose of Synchronise (and to a lesser extent, SyncRoot)

Brian Grunkemeyer described this on the BCL team blog a couple of years back: http://blogs.msdn.com/bclteam/archive/2005/03/15/396399.aspx

The key issue is getting the correct granularity around locks, where some developers would naively use multiple properties or methods on a "synchronized" collection and believe their code to be thread safe. Brian uses Queue as his example,

if (queue.Count > 0) {
    object obj = null;
    try {
        obj = queue.Dequeue();

Developers wouldn't realize that Count could be changed by another thread before Dequeue was invoked.

Forcing developers to use an explicit lock statement around the whole operation means preventing this false sense of security.

As Brian mentions, the removal of SyncRoot was partly because it had mainly been introduced to support Synchronized, but also because in many cases there is a better choice of lock object - most of the time, either the Queue instance itself, or a

private static object lockObjForQueueOperations = new object();

on the class owning the instance of the Queue...

This latter approach is usually safest as it avoids some other common traps:

As they say, threading is hard, and making it seem easy can be dangerous.