我希望有一个可以在多线程下安全使用的 List<T>
属性的实现。
类似于这样:
private List<T> _list;
private List<T> MyT
{
get { // return a copy of _list; }
set { _list = value; }
}
看起来我仍然需要返回集合的副本,这样如果我们在遍历集合的同时对集合进行设置,则不会引发异常。
如何实现一个线程安全的集合属性?
我希望有一个可以在多线程下安全使用的 List<T>
属性的实现。
类似于这样:
private List<T> _list;
private List<T> MyT
{
get { // return a copy of _list; }
set { _list = value; }
}
看起来我仍然需要返回集合的副本,这样如果我们在遍历集合的同时对集合进行设置,则不会引发异常。
如何实现一个线程安全的集合属性?
ConcurrentBag<T>
而不是 List<T>
。ConcurrentBag
是一个无序的集合,因此与 List<T>
不同,它不保证顺序。而且你不能通过索引访问元素。 - Radek Stromský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 中(这是客户端配置文件的一部分,但不是可移植类库)。我认为创建一个示例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;
}
}
在请求枚举器之前,你可以简单地克隆列表,这样任何枚举都是基于一个不能在运行时修改的副本进行的。
newList
不会添加或删除任何项目,这将使枚举器无效)。 - TejsClone()
方法可以简单地写成 lock(_lock) { return new List<T>(_internalList); }
。 - Mr Anderson尽管被接受的答案是ConcurrentBag,但我认为它并非所有情况下都能真正替代列表,正如Radek对答案的评论所说:“ConcurrentBag是无序集合,因此与List不同,它不能保证排序。此外,您无法通过索引访问项目。”
因此,如果您使用.NET 4.0或更高版本,一种解决方法是使用具有整数TKey作为数组索引和TValue作为数组值的ConcurrentDictionary。这是Pluralsight的C# Concurrent Collections course中替换列表的推荐方法。ConcurrentDictionary解决了上述两个问题:索引访问和排序(我们不能依赖排序,因为它在内部是哈希表,但当前的.NET实现保存了添加元素的顺序)。
ConcurrentDictionary
是一个字典,不是一个列表。2)正如你自己的回答所述,它不能保证保留顺序,这与你发布回答的原因相矛盾。3)它链接到一个视频,而没有将相关的引用带入此答案中(这可能也与其许可证不符)。 - jpmc26while (!myDict.TryAdd(myDict.Count, myValue)) { }
(或者在可能存在删除的情况下使用计数器的原子增量)。这将确保在检索值时可以将它们带回原始顺序。 - ChristophC#的ArrayList
类有一个Synchronized
方法。
var threadSafeArrayList = ArrayList.Synchronized(new ArrayList());
这将返回一个线程安全的包装器,可以包装任何IList
实例。为确保线程安全,所有操作都需要通过包装器执行。
回复自我的所有答案:
人们发现了这些解决方案:
SynchronizedList类最常见的投诉是:
可以实现更复杂的锁系统,例如针对单个行、一组行等。尽管如此,这将开始看起来像是实现自己的数据库。编写高性能集合是一门艺术,与您想要使用它的具体场景紧密相连。
我仍然相信对于一般使用场景,简单的解决方案是最通用的。
C5 collection库是伟大收藏设计的灵感之一,处理并发问题的方式如下:
原始答案:
如果您查看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创建更好的实现,但是没有去做。
_root
)在每次访问(读/写)时锁定会使其变得缓慢。也许让这个类保持内部状态会更好。 - Xaqronthis[index]
之后但在锁定被激活之前调用了Clear()
。index
不再安全使用,并且最终执行时将抛出异常。 - Suncat2000List<T>
的任何人都应该看看不可变集合,特别是ImmutableArray。看起来很多人想要一个线程安全的、索引动态大小的集合。我所知道的最接近且最容易使用的东西是。
System.Collections.Concurrent.ConcurrentDictionary<int, YourDataType>
.count()
,它可以作为您添加的任何新键值对的键。您也可以使用更原始的方法
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的简单性和透明度,它在防止崩溃方面扮演了重要角色。
IList<T>
实现(而不是List<T>
)吗? - Greg