除了并发集合是命名空间而
SynchronizedCollection<T>
是类之外,System.Collections.Concurrent
和 SynchronizedCollection<T>
命名空间中的并发集合有何不同?
SynchronizedCollection<T>
以及并发集合中的所有类都提供线程安全集合。我如何决定何时使用其中一种而不是另一种,为什么?
SynchronizedCollection<T>
类首先在.NET 2.0中引入,以提供线程安全的集合类。它通过锁定来实现这一点,因此您基本上拥有一个 List<T>
,其中每个访问都包含在 lock
语句中。
System.Collections.Concurrent
命名空间要更新得多。它直到 .NET 4.0 才被引入,它包括一组经过大幅改进且更加多样化的选择。这些类不再使用锁来提供线程安全性,这意味着它们应该在多个线程同时访问其数据的情况下更好地扩展。然而,这些选项中明显缺少实现 IList<T>
接口的类。
因此,如果您的目标是 .NET Framework 4.0 版本,则应尽可能使用
System.Collections.Concurrent
命名空间提供的集合之一。正如在 System.Collections.Generic
命名空间 中提供的各种类型的集合之间进行选择一样,您需要选择其功能和特性最适合您的特定需求的集合。
如果您的目标是较旧版本的 .NET Framework 或需要实现
IList<T>
接口的集合类,则必须选择 SynchronizedCollection<T>
类。
MSDN 上的这篇文章也值得一读:When to Use a Thread-Safe Collection
SynchronizedCollection<T>
是同步的 List<T>
。这是一个可以在一秒钟内设计出来的概念,并且可以在大约一小时内“完全实施”。只需将 List<T>
的每个方法包装在 lock (this)
中,就完成了。现在您有了一个线程安全的集合,它可以满足多线程应用程序的所有需求。但事实并非如此。一旦您尝试用它做任何重要的事情,SynchronizedCollection<T>
的缺点就会变得明显。特别是当您尝试将集合的两个或多个方法组合起来进行概念上的单一操作时。然后您意识到该操作不是原子的,并且如果不诉诸显式同步(锁定集合的
SyncRoot
属性)就无法使其成为原子操作,这破坏了集合的整个目的。一些例子:确保集合包含独特的元素:
if (!collection.Contains(x)) collection.Add(x);
Contains
和 Add
之间固有的竞争条件允许出现重复。确保集合最多包含 N 个元素:if (collection.Count < N) collection.Add(x);
Count
和 Add
之间的竞争条件允许集合中存在超过 N 个元素。将 "Foo"
"Bar"
:int index = collection.IndexOf("Foo"); if (index >= 0) collection[index] = "Bar";
。当线程读取 index
时,它的值立即失效。另一个线程可能会以 index
指向某个其他元素的方式更改集合,或者超出范围。
和
ConcurrentDictionary<K,V>
,是高度复杂的组件。它们比笨拙的
SynchronizedCollection<T>
复杂几个数量级。它们配备了非常适合多线程环境的特殊原子 API(TryDequeue
、GetOrAdd
、AddOrUpdate
等),并且还具有旨在最大限度地减少大量使用情况下争用的实现。在内部,它们采用无锁、低锁和粒度锁技术。学习如何使用这些集合需要一些研究。它们不是非并发对应物的直接替代品。注意: SynchronizedCollection<T>
的枚举不同步。使用
GetEnumerator
获取枚举器是同步的,但使用枚举器则不是。因此,如果一个线程执行 foreach (var item in collection)
,而另一个线程以任何方式改变集合(Add
、Remove
等),则程序的行为是未定义的。枚举 SynchronizedCollection<T>
的安全方法是获取集合的快照,然后枚举该快照。获取快照并不简单,因为它涉及两个方法调用(Count
getter 和 CopyTo
),因此需要显式同步。请注意 LINQ
ToArray
运算符,它本身不是线程安全的。下面是
ToArraySafe
类的安全 SynchronizedCollection<T>
扩展方法:/// <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;
}
}