开发者

What is the difference between SynchronizedCollection<T> and the other concurrent collections?

How does SynchronizedCollection<T> and the concurrent collections in the System.Collections.Concurrent namespace differ from each other, apart from Concurrent Collections being a namespace and SynchronizedCollection<T> being a class?

SynchronizedCollection<T开发者_运维知识库> and all of the classes in Concurrent Collections provide thread-safe collections. How do I decide when to use one over the other, and why?


The SynchronizedCollection<T> class was introduced first in .NET 2.0 to provide a thread-safe collection class. It does this via locking so that you essentially have a List<T> where every access is wrapped in a lock statement.

The System.Collections.Concurrent namespace is much newer. It wasn't introduced until .NET 4.0 and it includes a substantially improved and more diverse set of choices. These classes no longer use locks to provide thread safety, which means they should scale better in a situation where multiple threads are accessing their data simultaneously. However, a class implementing the IList<T> interface is notably absent among these options.

So, if you're targeting version 4.0 of the .NET Framework, you should use one of the collections provided by the System.Collections.Concurrent namespace whenever possible. Just as with choosing between the various types of collections provided in the System.Collections.Generic namespace, you'll need to choose the one whose features and characteristics best fit your specific needs.

If you're targeting an older version of the .NET Framework or need a collection class that implements the IList<T> interface, you'll have to opt for the SynchronizedCollection<T> class.

This article on MSDN is also worth a read: When to Use a Thread-Safe Collection


The SynchronizedCollection<T> is a synchronized List<T>. It's a concept that can be devised in a second, and can be implemented fully in about one hour. Just wrap each method of a List<T> inside a lock (this), and you are done. Now you have a thread-safe collection, that can cover all the needs of a multithreaded application. Except that it doesn't.

The shortcomings of the SynchronizedCollection<T> become apparent as soon as you try to do anything non-trivial with it. Specifically as soon as you try to combine two or more methods of the collection for a conceptually singular operation. Then you realize that the operation is not atomic, and cannot be made atomic without resorting to explicit synchronization (locking on the SyncRoot property of the collection), which undermines the whole purpose of the collection. Some examples:

  1. Ensure that the collection contains unique elements: if (!collection.Contains(x)) collection.Add(x);. This code ensures nothing. The inherent race condition between Contains and Add allows duplicates to occur.
  2. Ensure that the collection contains at most N elements: if (collection.Count < N) collection.Add(x);. The race condition between Count and Add allows more than N elements in the collection.
  3. Replace "Foo" with "Bar": int index = collection.IndexOf("Foo"); if (index >= 0) collection[index] = "Bar";. When a thread reads the index, its value is immediately stale. Another thread might change the collection in a way that the index points to some other element, or it's out of range.

At this point you realize that multithreading is more demanding than what you originally thought. Adding a layer of synchronization around the API of an existing collection doesn't cut it. You need a collection that is designed from the ground up for multithreaded usage, and has an API that reflects this design. This was the motivation for the introduction of the concurrent collections in .NET Framework 4.0.

The concurrent collections, for example the ConcurrentQueue<T> and the ConcurrentDictionary<K,V>, are highly sophisticated components. They are orders of magnitude more sophisticated than the clumsy SynchronizedCollection<T>. They are equipped with special atomic APIs that are well suited for multithreaded environments (TryDequeue, GetOrAdd, AddOrUpdate etc), and also with implementations that aim at minimizing the contention under heavy usage. Internally they employ lock-free, low-lock and granular-lock techniques. Learning how to use these collections requires some study. They are not direct drop-in replacements of their non-concurrent counterparts.

Caution: the enumeration of a SynchronizedCollection<T> is not synchronized. Getting an enumerator with GetEnumerator is synchronized, but using the enumerator is not. So if one thread does a foreach (var item in collection) while another thread mutates the collection in any way (Add, Remove etc), the behavior of the program is undefined. The safe way to enumerate a SynchronizedCollection<T> is to get a snapshot of the collection, and then enumerate the snapshot. Getting a snapshot is not trivial, because it involves two method calls (the Count getter and the CopyTo), so explicit synchronization is required. Beware of the LINQ ToArray operator, it's not thread-safe by itself. Below is a safe ToArraySafe extension method for the SynchronizedCollection<T> class:

/// <summary>Copies the elements of the collection to a new array.</summary>
public static T[] ToArraySafe<T>(this SynchronizedCollection<T> source)
{
    ArgumentNullException.ThrowIfNull(source);
    lock (source.SyncRoot)
    {
        T[] array = new T[source.Count];
        source.CopyTo(array, 0);
        return array;
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜