线程安全列表 < T > 属性

我希望实现 List<T>作为一个属性,可以使用线程安全没有任何疑问。

就像这样:

private List<T> _list;


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

看起来我仍然需要返回一个集合的副本(克隆) ,所以如果我们在迭代集合的某个地方同时设置了集合,那么就不会引发异常。

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

275154 次浏览

使用 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关键字,并根据你的具体情况定制它的用法。

如果需要,可以使用 _list变量在 getset块中使用 lock,这样就不会同时发生读/写操作。

如果您的目标是.Net 4,那么在 系统。收集。并发命名空间中有几个选项

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

基本上,如果希望安全枚举,就需要使用 lock。

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

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

此类型的公共静态(在 VisualBasic 中为 Shared)成员是线程安全的。不能保证任何实例成员都是线程安全的。

List 可以同时支持多个读取器,只要不修改集合。通过集合枚举本质上不是线程安全过程。在枚举与一个或多个写访问发生冲突的罕见情况下,确保线程安全的唯一方法是在整个枚举期间锁定集合。若要允许多个线程访问集合以进行读写,必须实现自己的同步。

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

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

无论如何,msdn 说,这确实是一个副本,而不仅仅是一个参考。哦,是的,您需要像其他人指出的那样锁定 set 方法。

我认为创建一个示例 ThreadSafeList 类应该很容易:

public class ThreadSafeList<T> : IList<T>
{
protected List<T> _internalList = 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)
{
_internalList.ForEach(x => newList.Add(x));
}


return newList;
}
}

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

还可以使用更原始的

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

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

如果您期望代码中有异常,这并不安全,但它允许您执行以下操作:

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 + 的简单性 + 透明性,它在阻止崩溃方面起到了重要作用

即使它得到了最多的投票,人们通常不能把 System.Collections.Concurrent.ConcurrentBag<T>作为线程安全的 System.Collections.Generic.List<T>的替代品,因为它是(Radek Stromský 已经指出)没有订购。

但是现在已经有一个叫 System.Collections.Generic.SynchronizedCollection<T>的类了。NET 3.0框架的一部分,但是它隐藏在一个人们意想不到的地方,这是鲜为人知的,可能你从来没有绊倒过它(至少我从来没有)。

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

甚至可以接受的答案是 ConcurrentBag,我不认为它在所有情况下都能真正取代 List,正如 Radek 对答案的评论所说: “ ConcurrentBag 是无序集合,所以不像 List,它不能保证有序。也不能通过索引访问项目”。

所以如果你用。NET 4.0或更高版本,解决方案可以是使用 并行词典作为整数 TKey 作为数组索引,使用 TValue 作为数组值。这是在 Pluralsight 的 C # 并发收集课程中替换 list 的推荐方法。ConcurrentDictionary 解决了上面提到的两个问题: 索引访问和排序(我们不能依赖排序,因为它是隐藏在引擎罩下的哈希表,而是最新的。NET 实现保存添加元素的顺序)。

C # 的 ArrayList类有一个 Synchronized方法。

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

这将返回任何 IList实例周围的线程安全包装器。所有操作都需要通过包装器执行,以确保线程安全。

如果您查看 List of T (https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877)的源代码,您会注意到那里有一个类(当然是内部的——为什么,微软,为什么? ! ?)名为 SynchronizedList of T。我正在复制粘贴代码:

   [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可以创建一个更好的实现,但他们没有做到这一点。

似乎很多发现这一点的人都想要一个线程安全索引的动态大小的集合。据我所知,最接近也是最简单的事情就是。

System.Collections.Concurrent.ConcurrentDictionary<int, YourDataType>

如果您想要正常的索引行为,这将需要您确保密钥正确地递增。如果您小心,.count()可以充当您添加的任何新键值对的键。

我建议任何处理多线程场景中的 List<T>的人看看 永恒收藏,特别是 ImmutableArray

我发现它非常有用,当你有:

  1. 列表中的项目相对较少
  2. 没有那么多的读/写操作
  3. 大量并发访问(例如,以读模式访问列表的许多线程)

当您需要实现某种类似事务的行为时(例如,在失败的情况下恢复插入/更新/删除操作) ,也可能很有用

在.NET Core (任何版本)中,您都可以使用 不可变列表,它具有 List<T>的所有功能。

下面是不带锁的线程安全列表类

 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;
}
}

看看最初的样本,人们可能会猜想,其目的是能够简单地用新的列表替换该列表。房子里的塞特告诉我们的。

Microsoft 的线程安全集合用于安全地从集合中添加和删除项。但是,如果在应用程序逻辑中,您打算用新的集合替换该集合,那么可以再次猜测,List 的添加和删除功能并不是必需的。

如果是这样的话,那么简单的答案就是使用 IReadOnlyList 接口:

 private IReadOnlyList<T> _readOnlyList = new List<T>();


private IReadOnlyList<T> MyT
{
get { return _readOnlyList; }
set { _readOnlyList = value; }
}

在这种情况下不需要使用任何锁定,因为没有办法修改集合。如果在 setter 中“ _ readOnlyList = value;”将被更复杂的东西替换,那么可能需要锁。