我想要一个List<T>
的实现作为一个属性,可以毫无疑问地使用线程安全。
这样的事情:
private List<T> _list;
private List<T> MyT
{
get { // return a copy of _list; }
set { _list = value; }
}
似乎我仍然需要返回一个集合(克隆)的集合,所以如果我们在某个地方迭代集合并同时设置集合,那么就不会引发异常。
如何实现线程安全的集合属性?
答案 0 :(得分:161)
如果您的目标是.Net 4,System.Collections.Concurrent命名空间
中有几个选项在这种情况下,您可以使用ConcurrentBag<T>
而不是List<T>
答案 1 :(得分:79)
即使获得最多的选票,通常也不能将System.Collections.Concurrent.ConcurrentBag<T>
作为System.Collections.Generic.List<T>
的线程安全替代品,因为它(RadekStromský已经指出)没有订购。
但是有一个名为System.Collections.Generic.SynchronizedCollection<T>
的类已经是.NET 3.0的一部分了,但是它隐藏在一个人们不期望的地方,它鲜为人知,可能你有从来没有偶然发现它(至少我从来没有)。
SynchronizedCollection<T>
被编译为程序集 System.ServiceModel.dll (它是客户端配置文件的一部分,但不是可移植类库的一部分)。
希望有所帮助。
答案 2 :(得分:16)
我认为制作一个示例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;
}
}
您只需在请求枚举器之前克隆列表,因此任何枚举都会处理运行时无法修改的副本。
答案 3 :(得分:5)
即使接受的答案是ConcurrentBag,我不认为它在所有情况下都是真正的替换列表,因为Radek对答案的评论说:“ConcurrentBag是无序集合,因此与List不同,它不保证订购。此外,您无法访问按索引“。
的项目因此,如果您使用.NET 4.0或更高版本,解决方法可能是使用 ConcurrentDictionary 将整数TKey作为数组索引,将TValue用作数组值。这是在Pluralsight的C# Concurrent Collections course中替换列表的推荐方法。 ConcurrentDictionary解决了上面提到的两个问题:索引访问和排序(我们不能依赖于排序,因为它的底层哈希表,但是当前的.NET实现保存了元素添加的顺序)。
答案 4 :(得分:4)
您可以使用:
var threadSafeArrayList = ArrayList.Synchronized(new ArrayList());
创建线程安全ArrayLsit
答案 5 :(得分:3)
如果查看T列表(https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877)的源代码,您会注意到那里有一个类(当然是内部的-为什么,Microsoft,为什么?!?!),称为T的SynchronizedList我在此处复制代码:
[Serializable()]
internal class SynchronizedList : IList<T> {
private List<T> _list;
private Object _root;
internal SynchronizedList(List<T> list) {
_list = list;
_root = ((System.Collections.ICollection)list).SyncRoot;
}
public int Count {
get {
lock (_root) {
return _list.Count;
}
}
}
public bool IsReadOnly {
get {
return ((ICollection<T>)_list).IsReadOnly;
}
}
public void Add(T item) {
lock (_root) {
_list.Add(item);
}
}
public void Clear() {
lock (_root) {
_list.Clear();
}
}
public bool Contains(T item) {
lock (_root) {
return _list.Contains(item);
}
}
public void CopyTo(T[] array, int arrayIndex) {
lock (_root) {
_list.CopyTo(array, arrayIndex);
}
}
public bool Remove(T item) {
lock (_root) {
return _list.Remove(item);
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
lock (_root) {
return _list.GetEnumerator();
}
}
IEnumerator<T> IEnumerable<T>.GetEnumerator() {
lock (_root) {
return ((IEnumerable<T>)_list).GetEnumerator();
}
}
public T this[int index] {
get {
lock(_root) {
return _list[index];
}
}
set {
lock(_root) {
_list[index] = value;
}
}
}
public int IndexOf(T item) {
lock (_root) {
return _list.IndexOf(item);
}
}
public void Insert(int index, T item) {
lock (_root) {
_list.Insert(index, item);
}
}
public void RemoveAt(int index) {
lock (_root) {
_list.RemoveAt(index);
}
}
}
我个人认为他们知道可以使用SemaphoreSlim创建更好的实现,但是没有实现。
答案 6 :(得分:2)
您还可以使用更原始的
Monitor.Enter(lock);
Monitor.Exit(lock);
哪个锁使用(请参阅此帖子C# Locking an object that is reassigned in lock block)。
如果您希望代码中存在异常,则这不安全,但它允许您执行以下操作:
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");
}
}
}
关于这一点的一个好处是你将在一系列操作的持续时间内获得锁定(而不是锁定每个操作)。这意味着输出应该出现在正确的块中(我的用法是从外部进程获得一些输出到屏幕上)
我真的很喜欢ThreadSafeList +的简单性和透明性,它在阻止崩溃方面发挥了重要作用
答案 7 :(得分:1)
我相信_list.ToList()
会让你复制一份。如果需要,您也可以查询:
_list.Select("query here").ToList();
无论如何,msdn说这确实是一个副本而不仅仅是一个参考。哦,是的,你需要像其他人指出的那样锁定set方法。
答案 8 :(得分:1)
似乎很多人发现他们想要线程安全索引的动态大小的集合。我所知道的最接近和最简单的事情就是。
System.Collections.Concurrent.ConcurrentDictionary<int, YourDataType>
如果您想要正常的索引行为,这将需要您确保对密钥进行正确的识别。如果您小心的话,.count可以作为添加的任何新键值对的键。
答案 9 :(得分:0)
我建议在多线程场景中处理List<T>
的任何人都来研究Immutable Collections,尤其是ImmutableArray。
当您具备以下条件时,我发现它非常有用:
当您需要实现某种类似于事务的行为(即在失败的情况下还原插入/更新/删除操作)时,也很有用
答案 10 :(得分:0)
在.NET Core(任何版本)中,您可以使用ImmutableList,它具有List<T>
的所有功能。
答案 11 :(得分:0)
这是不带锁的线程安全列表的类
public class ConcurrentList
{
private long _i = 1;
private ConcurrentDictionary<long, T> dict = new ConcurrentDictionary<long, T>();
public int Count()
{
return dict.Count;
}
public List<T> ToList()
{
return dict.Values.ToList();
}
public T this[int i]
{
get
{
long ii = dict.Keys.ToArray()[i];
return dict[ii];
}
}
public void Remove(T item)
{
T ov;
var dicItem = dict.Where(c => c.Value.Equals(item)).FirstOrDefault();
if (dicItem.Key > 0)
{
dict.TryRemove(dicItem.Key, out ov);
}
this.CheckReset();
}
public void RemoveAt(int i)
{
long v = dict.Keys.ToArray()[i];
T ov;
dict.TryRemove(v, out ov);
this.CheckReset();
}
public void Add(T item)
{
dict.TryAdd(_i, item);
_i++;
}
public IEnumerable<T> Where(Func<T, bool> p)
{
return dict.Values.Where(p);
}
public T FirstOrDefault(Func<T, bool> p)
{
return dict.Values.Where(p).FirstOrDefault();
}
public bool Any(Func<T, bool> p)
{
return dict.Values.Where(p).Count() > 0 ? true : false;
}
public void Clear()
{
dict.Clear();
}
private void CheckReset()
{
if (dict.Count == 0)
{
this.Reset();
}
}
private void Reset()
{
_i = 1;
}
}
答案 12 :(得分:-1)
以下是您要求的课程:
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 );
}
}
}
}
}
}
答案 13 :(得分:-1)
查看原始样本可能会猜测其意图是能够简单地用新样本替换列表。财产的设定者告诉我们这件事。
Micrisoft的线程安全集合用于安全地从集合中添加和删除项目。但是,如果您打算在应用程序逻辑中用新集合替换集合,则可能会再次猜测,不需要列表的添加和删除功能。
如果是这种情况,那么简单的答案就是使用IReadOnlyList接口:
private IReadOnlyList<T> _readOnlyList = new List<T>();
private IReadOnlyList<T> MyT
{
get { return _readOnlyList; }
set { _readOnlyList = value; }
}
在这种情况下,无需使用任何锁定,因为无法修改集合。如果在setter中为“ _readOnlyList = value;”将被更复杂的替换,然后可能需要锁定。
答案 14 :(得分:-3)
基本上如果你想安全地枚举,你需要使用锁。
请参阅MSDN。 http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx
以下是您可能感兴趣的MSDN的一部分:
此类型的公共静态(在Visual Basic中为Shared)成员是线程安全的。不保证任何实例成员都是线程安全的。
只要未修改集合,List就可以同时支持多个读取器。枚举通过集合本质上不是线程安全的过程。在枚举与一个或多个写访问争用的极少数情况下,确保线程安全的唯一方法是在整个枚举期间锁定集合。要允许多个线程访问集合以进行读写,您必须实现自己的同步。
答案 15 :(得分:-13)
使用lock
语句执行此操作。 (Read here for more information.)
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
关键字,并根据您的具体情况定制其用途。
如果需要,您可以在lock
和get
块中set
使用_list
变量进行{{1}},这样就不会发生读/写操作在同一时间。