线程安全列表<T>属性

我想要List<T>一个实现作为一个属性,可以毫无疑问地使用线程安全。

像这样的东西:

 private List<T> _list; private List<T> MyT { get { // return a copy of _list; } set { _list = value; } } 

似乎仍然需要返回一个集合的副本(克隆),所以如果我们迭代集合的地方,同时集合被设置,那么不会引发exception。

如何实现线程安全的集合属性?

如果您的目标是.Net 4,则在System.Collections.Concurrent命名空间中有几个选项

在这种情况下,您可以使用ConcurrentBag<T>而不是List<T>

即使它获得了最多的选票,通常也不能将System.Collections.Concurrent.ConcurrentBag<T>作为System.Collections.Concurrent.ConcurrentBag<T>的线程安全replace(RadekStromský已经指出它出)不订购。

但是有一个名为System.Collections.Generic.SynchronizedCollection<T>的类,它已经从框架的.NET 3.0部分开始,但是它隐藏在一个没有期望的位置,你从来没有偶然发现过(至less我从来没有)。

SynchronizedCollection<T>被编译为程序集System.ServiceModel.dll (它是客户端configuration文件的一部分,但不是可移植类库的一部分)。

希望有所帮助。

我会想,做一个示例ThreadSafeList类将是很容易的:

 public class ThreadSafeList<T> : IList<T> { protected List<T> _interalList = new List<T>(); // Other Elements of IList implementation public IEnumerator<T> GetEnumerator() { return Clone().GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return Clone().GetEnumerator(); } protected static object _lock = new object(); public List<T> Clone() { List<T> newList = new List<T>(); lock (_lock) { _interalList.ForEach(x => newList.Add(x)); } return newList; } } 

您只需在请求枚举器之前克隆列表即可,因此任何枚举都会在运行时无法修改的副本上工作。

我相信_list.ToList()会让你一个副本。 你也可以查询它,如果你需要如:

 _list.Select("query here").ToList(); 

无论如何,msdn说这确实是一个副本,而不仅仅是一个参考。 噢,是的,你需要像别人指出的那样locking设定的方法。

你也可以使用更原始的

 Monitor.Enter(lock); Monitor.Exit(lock); 

哪个锁使用(见这篇文章C#locking在locking块中重新分配的对象 )。

如果您在代码中期望exception,这是不安全的,但它可以让你做下面的事情:

 using System; using System.Collections.Generic; using System.Threading; using System.Linq; public class Something { private readonly object _lock; private readonly List<string> _contents; public Something() { _lock = new object(); _contents = new List<string>(); } public Modifier StartModifying() { return new Modifier(this); } public class Modifier : IDisposable { private readonly Something _thing; public Modifier(Something thing) { _thing = thing; Monitor.Enter(Lock); } public void OneOfLotsOfDifferentOperations(string input) { DoSomethingWith(input); } private void DoSomethingWith(string input) { Contents.Add(input); } private List<string> Contents { get { return _thing._contents; } } private object Lock { get { return _thing._lock; } } public void Dispose() { Monitor.Exit(Lock); } } } public class Caller { public void Use(Something thing) { using (var modifier = thing.StartModifying()) { modifier.OneOfLotsOfDifferentOperations("A"); modifier.OneOfLotsOfDifferentOperations("B"); modifier.OneOfLotsOfDifferentOperations("A"); modifier.OneOfLotsOfDifferentOperations("A"); modifier.OneOfLotsOfDifferentOperations("A"); } } } 

其中一件好事就是在一系列操作期间(而不是在每个操作中locking)获得locking。 这意味着输出应该出现在正确的块(我使用这个是从外部进程获得一些输出到屏幕上)

我真的很喜欢ThreadSafeList +的简单性+透明性,在停止崩溃方面起到了重要作用

这是你要求的课程:

 namespace AI.Collections { using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; /// <summary> /// Just a simple thread safe collection. /// </summary> /// <typeparam name="T"></typeparam> /// <value>Version 1.5</value> /// <remarks>TODO replace locks with AsyncLocks</remarks> [DataContract( IsReference = true )] public class ThreadSafeList<T> : IList<T> { /// <summary> /// TODO replace the locks with a ReaderWriterLockSlim /// </summary> [DataMember] private readonly List<T> _items = new List<T>(); public ThreadSafeList( IEnumerable<T> items = null ) { this.Add( items ); } public long LongCount { get { lock ( this._items ) { return this._items.LongCount(); } } } public IEnumerator<T> GetEnumerator() { return this.Clone().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public void Add( T item ) { if ( Equals( default( T ), item ) ) { return; } lock ( this._items ) { this._items.Add( item ); } } public Boolean TryAdd( T item ) { try { if ( Equals( default( T ), item ) ) { return false; } lock ( this._items ) { this._items.Add( item ); return true; } } catch ( NullReferenceException ) { } catch ( ObjectDisposedException ) { } catch ( ArgumentNullException ) { } catch ( ArgumentOutOfRangeException ) { } catch ( ArgumentException ) { } return false; } public void Clear() { lock ( this._items ) { this._items.Clear(); } } public bool Contains( T item ) { lock ( this._items ) { return this._items.Contains( item ); } } public void CopyTo( T[] array, int arrayIndex ) { lock ( this._items ) { this._items.CopyTo( array, arrayIndex ); } } public bool Remove( T item ) { lock ( this._items ) { return this._items.Remove( item ); } } public int Count { get { lock ( this._items ) { return this._items.Count; } } } public bool IsReadOnly { get { return false; } } public int IndexOf( T item ) { lock ( this._items ) { return this._items.IndexOf( item ); } } public void Insert( int index, T item ) { lock ( this._items ) { this._items.Insert( index, item ); } } public void RemoveAt( int index ) { lock ( this._items ) { this._items.RemoveAt( index ); } } public T this[ int index ] { get { lock ( this._items ) { return this._items[ index ]; } } set { lock ( this._items ) { this._items[ index ] = value; } } } /// <summary> /// Add in an enumerable of items. /// </summary> /// <param name="collection"></param> /// <param name="asParallel"></param> public void Add( IEnumerable<T> collection, Boolean asParallel = true ) { if ( collection == null ) { return; } lock ( this._items ) { this._items.AddRange( asParallel ? collection.AsParallel().Where( arg => !Equals( default( T ), arg ) ) : collection.Where( arg => !Equals( default( T ), arg ) ) ); } } public Task AddAsync( T item ) { return Task.Factory.StartNew( () => { this.TryAdd( item ); } ); } /// <summary> /// Add in an enumerable of items. /// </summary> /// <param name="collection"></param> public Task AddAsync( IEnumerable<T> collection ) { if ( collection == null ) { throw new ArgumentNullException( "collection" ); } var produce = new TransformBlock<T, T>( item => item, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount } ); var consume = new ActionBlock<T>( action: async obj => await this.AddAsync( obj ), dataflowBlockOptions: new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount } ); produce.LinkTo( consume ); return Task.Factory.StartNew( async () => { collection.AsParallel().ForAll( item => produce.SendAsync( item ) ); produce.Complete(); await consume.Completion; } ); } /// <summary> /// Returns a new copy of all items in the <see cref="List{T}" />. /// </summary> /// <returns></returns> public List<T> Clone( Boolean asParallel = true ) { lock ( this._items ) { return asParallel ? new List<T>( this._items.AsParallel() ) : new List<T>( this._items ); } } /// <summary> /// Perform the <paramref name="action" /> on each item in the list. /// </summary> /// <param name="action"> /// <paramref name="action" /> to perform on each item. /// </param> /// <param name="performActionOnClones"> /// If true, the <paramref name="action" /> will be performed on a <see cref="Clone" /> of the items. /// </param> /// <param name="asParallel"> /// Use the <see cref="ParallelQuery{TSource}" /> method. /// </param> /// <param name="inParallel"> /// Use the /// <see /// cref="Parallel.ForEach{TSource}(System.Collections.Generic.IEnumerable{TSource},System.Action{TSource})" /> /// method. /// </param> public void ForEach( Action<T> action, Boolean performActionOnClones = true, Boolean asParallel = true, Boolean inParallel = false ) { if ( action == null ) { throw new ArgumentNullException( "action" ); } var wrapper = new Action<T>( obj => { try { action( obj ); } catch ( ArgumentNullException ) { //if a null gets into the list then swallow an ArgumentNullException so we can continue adding } } ); if ( performActionOnClones ) { var clones = this.Clone( asParallel: asParallel ); if ( asParallel ) { clones.AsParallel().ForAll( wrapper ); } else if ( inParallel ) { Parallel.ForEach( clones, wrapper ); } else { clones.ForEach( wrapper ); } } else { lock ( this._items ) { if ( asParallel ) { this._items.AsParallel().ForAll( wrapper ); } else if ( inParallel ) { Parallel.ForEach( this._items, wrapper ); } else { this._items.ForEach( wrapper ); } } } } /// <summary> /// Perform the <paramref name="action" /> on each item in the list. /// </summary> /// <param name="action"> /// <paramref name="action" /> to perform on each item. /// </param> /// <param name="performActionOnClones"> /// If true, the <paramref name="action" /> will be performed on a <see cref="Clone" /> of the items. /// </param> /// <param name="asParallel"> /// Use the <see cref="ParallelQuery{TSource}" /> method. /// </param> /// <param name="inParallel"> /// Use the /// <see /// cref="Parallel.ForEach{TSource}(System.Collections.Generic.IEnumerable{TSource},System.Action{TSource})" /> /// method. /// </param> public void ForAll( Action<T> action, Boolean performActionOnClones = true, Boolean asParallel = true, Boolean inParallel = false ) { if ( action == null ) { throw new ArgumentNullException( "action" ); } var wrapper = new Action<T>( obj => { try { action( obj ); } catch ( ArgumentNullException ) { //if a null gets into the list then swallow an ArgumentNullException so we can continue adding } } ); if ( performActionOnClones ) { var clones = this.Clone( asParallel: asParallel ); if ( asParallel ) { clones.AsParallel().ForAll( wrapper ); } else if ( inParallel ) { Parallel.ForEach( clones, wrapper ); } else { clones.ForEach( wrapper ); } } else { lock ( this._items ) { if ( asParallel ) { this._items.AsParallel().ForAll( wrapper ); } else if ( inParallel ) { Parallel.ForEach( this._items, wrapper ); } else { this._items.ForEach( wrapper ); } } } } } } 

即使接受的答案是ConcurrentBag,我不认为它是真正的所有情况下的列表replace,因为Radek的回答评论说:“ConcurrentBag是无序集合,所以不像列表不能保证sorting。 ”。

因此,如果您使用.NET 4.0或更高版本,解决方法可能是使用ConcurrentDictionary与整数TKey作为数组索引和TValue作为数组值。 这是在Pluralsight的C#Concurrent Collections课程中replace列表的推荐方式。 ConcurrentDictionary解决了上面提到的两个问题:索引访问和sorting(我们不能依靠sorting,因为它是哈希表下的哈希表,但是当前的.NET实现可以节省添加元素的顺序)。

您可以使用:

 var threadSafeArrayList = ArrayList.Synchronized(new ArrayList()); 

创build线程安全ArrayLsit

基本上如果你想安全枚举,你需要使用锁。

请参考MSDN。 http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

以下是您可能感兴趣的MSDN的一部分:

此types的公共静态(在Visual Basic中为Shared)成员是线程安全的。 任何实例成员不保证是线程安全的。

一个列表可以同时支持多个阅读器,只要该集合没有被修改。 枚举枚举本质上不是一个线程安全的过程。 在枚举与一个或多个写入访问竞争的罕见情况下,确保线程安全的唯一方法是在整个枚举期间locking集合。 为了让集合可以被多个线程读取和写入,您必须实现自己的同步。

使用lock语句来做到这一点。 (请阅读此处了解更多信息。 )

 private List<T> _list; private List<T> MyT { get { return _list; } set { //Lock so only one thread can change the value at any given time. lock (_list) { _list = value; } } } 

仅供参考,这可能不是你的要求 – 你可能想锁在你的代码更远,但我不能假设。 看看lock关键字,并根据您的具体情况量身定做。

如果需要的话,可以使用_listvariableslock getset块,这样就可以使读/写不能同时发生。