除了SynchronizedCollection<T>
是一个类,而System.Collections.Concurrent
命名空间中的并发集合是一个命名空间之外,SynchronizedCollection<T>
和所有Concurrent Collections类都提供了线程安全的集合。我如何决定什么情况下使用其中之一,以及为什么?
除了SynchronizedCollection<T>
是一个类,而System.Collections.Concurrent
命名空间中的并发集合是一个命名空间之外,SynchronizedCollection<T>
和所有Concurrent Collections类都提供了线程安全的集合。我如何决定什么情况下使用其中之一,以及为什么?
List<T>
,其中每个访问都包裹在 lock
语句中。
System.Collections.Concurrent 命名空间要新得多。它直到 .NET 4.0 才被引入,并包括一组更为完善和多样化的选择。这些类不再使用锁来提供线程安全性,这意味着它们在多个线程同时访问其数据的情况下应该能够更好地扩展。但是,这些选项中明显缺少实现 IList<T>
接口的类。System.Collections.Concurrent
命名空间提供的集合之一。就像在选择 System.Collections.Generic
命名空间 中提供的各种类型的集合时一样,您需要选择最适合特定需求的功能和特性的集合。IList<T>
接口的集合类,则必须选择 SynchronizedCollection<T>
类。List<T>
。这是一个可以在一秒钟内设计出来,并且可以在大约一个小时内完全实现的概念。只需将List<T>
的每个方法包装在lock (this)
中,就完成了。现在你有了一个线程安全的集合,可以满足多线程应用程序的所有需求。除了它并不是完美的。"
< p >在尝试使用SynchronizedCollection<T>
进行任何非平凡操作时,它的缺点就会显现出来。特别是当您尝试将两个或多个集合方法组合为一个概念上的单一操作时,就会意识到该操作不是原子性的,并且不能通过隐式同步来实现原子性(需要锁定集合的SyncRoot
属性),这破坏了集合的整个目的。以下是一些例子:
确保集合包含唯一元素:if (!collection.Contains(x)) collection.Add(x);
。这段代码并不能起到确保的作用。Contains
和 Add
之间的竞争条件会导致重复元素出现。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
指向其他元素或超出范围。ConcurrentQueue<T>
和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
操作符本身不是线程安全的。下面是 SynchronizedCollection<T>
类的安全 ToArraySafe
扩展方法:/// <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;
}
}