如何创建线程安全的通用列表?
我有一个通用列表如下
public static readonly List Customers = new List();
我正在使用以下方法:
.Add .Find .FirstOrDefault
最后2个是LINQ扩展。
我需要使这个线程安全的能够运行容器类的多个实例。
怎么实现呢?
如果这些是您在List
上使用的唯一函数,那么最简单的方法是编写一个快速包装器,将访问与lock
同步
class MyList { private List _list = new List (); private object _sync = new object(); public void Add(T value) { lock (_sync) { _list.Add(value); } } public bool Find(Predicate predicate) { lock (_sync) { return _list.Find(predicate); } } public T FirstOrDefault() { lock (_sync) { return _list.FirstOrDefault(); } } }
我强烈推荐新类型+私有锁对象的方法。 对于inheritance你的代码的下一个人来说,实际意图是什么让它变得更加明显。
另请注意,.Net 4.0引入了一组新的集合,专门用于从多个线程中使用。 如果其中一个满足您的需求,我强烈建议您使用它自己滚动。
-
ConcurrentStack
-
ConcurrentQueue
如果您使用的是.NET Framework 4或更高版本,则可以使用线程安全集合 。
您可以用ConcurrentBag
替换List
ConcurrentBag
:
namespace Playground.Sandbox { using System.Collections.Concurrent; using System.Threading.Tasks; public static class Program { public static void Main() { var items = new[] { "Foo", "Bar", "Baz" }; var bag = new ConcurrentBag(); Parallel.ForEach(items, bag.Add); } } }
为了扩展@ JaradPar的答案,这里是一个完整的实现,带有一些额外的function,如摘要中所述
/// /// a thread-safe list with support for: /// 1) negative indexes (read from end). "myList[-1]" gets the last value /// 2) modification while enumerating: enumerates a copy of the collection. /// /// public class ConcurrentList : IList { private object _lock = new object(); private List _storage = new List (); /// /// support for negative indexes (read from end). "myList[-1]" gets the last value /// /// /// public TValue this[int index] { get { lock (_lock) { if (index < 0) { index = this.Count - index; } return _storage[index]; } } set { lock (_lock) { if (index < 0) { index = this.Count - index; } _storage[index] = value; } } } public void Sort() { lock (_lock) { _storage.Sort(); } } public int Count { get { return _storage.Count; } } bool ICollection .IsReadOnly { get { return ((IList )_storage).IsReadOnly; } } public void Add(TValue item) { lock (_lock) { _storage.Add(item); } } public void Clear() { lock (_lock) { _storage.Clear(); } } public bool Contains(TValue item) { lock (_lock) { return _storage.Contains(item); } } public void CopyTo(TValue[] array, int arrayIndex) { lock (_lock) { _storage.CopyTo(array, arrayIndex); } } public int IndexOf(TValue item) { lock (_lock) { return _storage.IndexOf(item); } } public void Insert(int index, TValue item) { lock (_lock) { _storage.Insert(index, item); } } public bool Remove(TValue item) { lock (_lock) { return _storage.Remove(item); } } public void RemoveAt(int index) { lock (_lock) { _storage.RemoveAt(index); } } public IEnumerator GetEnumerator() { lock (_lock) { lock (_lock) { return (IEnumerator )_storage.ToArray().GetEnumerator(); } } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }
您将需要在修改或迭代集合的每个位置使用锁。
或者使用其中一个新的线程安全数据结构,如ConcurrentBag 。
在操作集合时使用lock关键字,即:您的添加/查找:
lock(Customers) { Customers.Add(new Customer()); }
只有在任何私有对象上使用锁定,才能使您的Action可访问
请参阅:线程安全通用队列类
http://www.codeproject.com/Articles/38908/Thread-Safe-Generic-Queue-Class
好的,所以我不得不完全重写我的答案。 经过2天的测试后,我不得不说JasonS的代码有一些缺陷,我猜是因为有了枚举器。 当一个线程使用foreach,而另一个线程更改列表时,它会抛出exception。
所以我找到了这个答案 ,它在过去48小时不间断地对我很有用,我想在我的应用程序中创建了超过100k个线程,并使用了该列表。
我改变的唯一一件事 – 我已经进入了try-finally部分之外的锁定。 请在此处阅读可能的例外情况。 此外,如果您将阅读MSDN,他们有相同的方法。
但是,正如下面链接中提到的,List不能100%线程安全,这可能就是为什么c#中没有默认的ConcurentList实现。
切勿将ConcurrangBag用于订购数据。 请改用Array