线程安全列表财产
我想要一个 List
的实现作为一个属性,毫无疑问可以线程安全地使用。
像这样的事情:
private List<T> _list;
private List<T> MyT
{
get { // return a copy of _list; }
set { _list = value; }
}
我似乎仍然需要返回集合的副本(克隆),因此如果我们在某个地方迭代集合并同时设置集合,则不会引发异常。
如何实现线程安全的集合属性?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
如果您的目标是 .Net 4,系统中有一些选项。 Collections.Concurrent 命名空间
在这种情况下,您可以使用
ConcurrentBag
而不是List
If you are targetting .Net 4 there are a few options in System.Collections.Concurrent Namespace
You could use
ConcurrentBag<T>
in this case instead ofList<T>
即使它获得了最多的选票,通常也不能将 System.Collections.Concurrent.ConcurrentBag 作为
System.Collections.Generic.List 的线程安全替代品;
照原样(Radek Stromský 已经指出)未订购。但是有一个名为
System.Collections.Generic.SynchronizedCollection
的类,自 .NET 3.0 起就已成为框架的一部分,但它隐藏在人们意想不到的位置它鲜为人知,而且可能你从来没有偶然发现过它(至少我从来没有)。SynchronizedCollection
被编译为程序集System.ServiceModel.dll(它是客户端配置文件的一部分,但不是可移植类库的一部分)。编辑:还有一个 NuGet 包“System.ServiceModel.Primitives”,自版本 ~4.6.0 起包含
System.Collections.Generic.SynchronizedCollection
。< br>(归功于 Roi Shabtai)Even as it got the most votes, one usually can't take
System.Collections.Concurrent.ConcurrentBag<T>
as a thread-safe replacement forSystem.Collections.Generic.List<T>
as it is (Radek Stromský already pointed it out) not ordered.But there is a class called
System.Collections.Generic.SynchronizedCollection<T>
that is already since .NET 3.0 part of the framework, but it is that well hidden in a location where one does not expect it that it is little known and probably you have never ever stumbled over it (at least I never did).SynchronizedCollection<T>
is compiled into assembly System.ServiceModel.dll (which is part of the client profile but not of the portable class library).Edit: There is also a NuGet package "System.ServiceModel.Primitives" that since version ~4.6.0 contains the
System.Collections.Generic.SynchronizedCollection<T>
.(Credits to Roi Shabtai)
C# 的 ArrayList 类有一个
同步
方法。这将返回一个围绕
IList
的任何实例的线程安全包装器。所有操作都需要通过包装器来执行,以保证线程安全。但请注意,这是通过锁定集合来实现的,这可能会影响性能。来自MS 文档:
这对您来说可能不会影响交易,但值得记住。
C#'s
ArrayList
class has aSynchronized
method.This returns a thread safe wrapper around any instance of
IList
. All operations need to be performed through the wrapper to ensure thread safety.Note, however, that this works by locking the collection, which can have performance impact. From the MS docs:
This may not be a deal breaker for you– but it's worth keeping in mind.
我认为制作一个示例 ThreadSafeList 类会很容易:
您只需在请求枚举器之前克隆列表,因此任何枚举都会处理运行时无法修改的副本。
I would think making a sample ThreadSafeList class would be easy:
You simply clone the list before requesting an enumerator, and thus any enumeration is working off a copy that can't be modified while running.
即使接受的答案是 ConcurrentBag,我也不认为它在所有情况下都是列表的真正替代品,正如 Radek 对答案的评论所说:“ConcurrentBag 是无序集合,因此与 List 不同,它不能保证排序。此外,您无法通过索引访问项目”。
因此,如果您使用 .NET 4.0 或更高版本,解决方法可能是使用 ConcurrentDictionary,其中整数 TKey 作为数组索引,TValue 作为数组值。这是 Pluralsight 的 C# 并发集合课程 中推荐的替换列表的方法。 ConcurrentDictionary 解决了上面提到的两个问题:索引访问和排序(我们不能依赖排序,因为它是底层的哈希表,但当前的 .NET 实现保存了元素添加的顺序)。
Even accepted answer is ConcurrentBag, I don't think it's real replacement of list in all cases, as Radek's comment to the answer says: "ConcurrentBag is unordered collection, so unlike List it does not guarantee ordering. Also you cannot access items by index".
So if you use .NET 4.0 or higher, a workaround could be to use ConcurrentDictionary with integer TKey as array index and TValue as array value. This is recommended way of replacing list in Pluralsight's C# Concurrent Collections course. ConcurrentDictionary solves both problems mentioned above: index accessing and ordering (we can not rely on ordering as it's hash table under the hood, but current .NET implementation saves order of elements adding).
在 .NET Core(任何版本)中,您可以使用 ImmutableList,它具有
List
的所有功能。In .NET Core (any version), you can use ImmutableList, which has all the functionality of
List<T>
.回复自我最初的答案以来的所有答案:
人们找到了这些解决方案:
大多数针对 SynchronizedList 类的抱怨都与以下方面有关:
可以实现更复杂的锁系统,例如单个行,行组等。这将开始看起来像是实现您自己的数据库,尽管。编写高性能集合是一门艺术,并且与您想要使用它的特定场景紧密结合。
我仍然相信,对于一般使用场景,这里的简单解决方案是最通用的。
C5 集合库是伟大集合设计的灵感之一,它如此处理并发性:
原始答案:
如果您查看 T 列表的源代码(https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877)你会注意到那里有一个类(当然是内部的 -为什么,微软,为什么?!?!)称为T的SynchronizedList。我将代码复制粘贴到此处:
我个人认为他们知道使用 SemaphoreSlim 可以创建,但没有到达它。
In reply to all of the answers since my original one:
People found these solutions:
Most complaints against the SynchronizedList class were related to:
A more complex system of locks can be implemented, like for individual rows, groups of rows, etc. That will start to look like implementing your own database, though. Writing a high performance collection is an art and is very tightly coupled to the specific scenario you want to use it in.
I still believe that for a general use scenario the simple solution here is the most versatile.
The C5 collection library, which is one of the inspirations for great collection design, handles concurrency thus:
Original answer:
If you look at the source code for List of T (https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877) you will notice there is a class there (which is of course internal - why, Microsoft, why?!?!) called SynchronizedList of T. I am copy pasting the code here:
Personally I think they knew a better implementation using SemaphoreSlim could be created, but didn't get to it.
我建议任何在多线程场景中处理
List
的人看看 不可变集合 特别是 ImmutableArray。我发现它在以下情况下非常有用:
当您需要实现时也很有用某种类似事务的行为(即在失败时恢复插入/更新/删除操作)
I would suggest anyone dealing with a
List<T>
in multi-threading scenarios to take look at Immutable Collections in particular the ImmutableArray.I've found it very useful when you have:
Also can be useful when you need to implement some sort of transaction-like behavior (i.e. revert an insert/update/delete operation in case of fail)
似乎许多发现此问题的人都想要一个线程安全的索引动态大小的集合。我所知道的最接近和最简单的事情是。
如果您想要正常的索引行为,这将要求您确保您的密钥正确递增。如果您小心的话,
.count()
足以作为您添加的任何新键值对的键。It seems like many of the people finding this are wanting a thread safe indexed dynamically sized collection. The closest and easiest thing I know of would be.
This would require you to ensure your key is properly incremented if you want normal indexing behavior. If you are careful
.count()
could suffice as the key for any new key value pairs you add.您还可以使用更原始的
锁使用(请参阅这篇文章C# 锁定在锁块中重新分配的对象)。
如果您期望代码中出现异常,那么这并不安全,但它允许您执行类似以下操作:
这样做的好处之一是您将在一系列操作的持续时间内获得锁定(而不是锁定)每个操作)。这意味着输出应该以正确的块形式出现(我对此的使用是从外部进程将一些输出显示到屏幕上)
我真的很喜欢 ThreadSafeList 的简单性 + 透明度 + 它在阻止崩溃方面发挥了重要作用
You can also use the more primitive
which lock uses (see this post C# Locking an object that is reassigned in lock block).
If you are expecting exceptions in the code this is not safe but it allows you to do something like the following:
One of the nice things about this is you'll get the lock for the duration of the series of operations (rather than locking in each operation). Which means that the output should come out in the right chunks (my usage of this was getting some output onto screen from an external process)
I do really like the simplicity + transparency of the ThreadSafeList + that does the important bit in stopping crashes
我相信
_list.ToList()
会给你一份副本。如果需要,您也可以查询它,例如:无论如何,msdn 说这确实是一个副本,而不仅仅是一个参考。哦,是的,您需要锁定 set 方法,正如其他人指出的那样。
I believe
_list.ToList()
will make you a copy. You can also query it if you need to such as :Anyways, msdn says this is indeed a copy and not simply a reference. Oh, and yes, you will need to lock in the set method as the others have pointed out.
查看原始示例,人们可能会猜测其目的是能够简单地用新列表替换列表。该房产的设置者告诉我们这件事。
Micrisoft 的线程安全集合用于安全地添加和删除集合中的项目。但是,如果在应用程序逻辑中您打算用新集合替换该集合,人们可能会再次猜测,不需要 List 的添加和删除功能。
如果是这种情况,简单的答案是使用 IReadOnlyList 接口:
在这种情况下不需要使用任何锁定,因为无法修改集合。如果在设置器中“_readOnlyList = value;”将被更复杂的东西所取代,然后可能需要锁。
Looking at the original sample one may guess that the intention was to be able to simply replace the list with the new one. The setter on the property tells us about it.
The Micrisoft's Thread-Safe Collections are for safely adding and removing items from collection. But if in the application logic you are intending to replace the collection with the new one, one may guess, again, that the adding and deleting functionality of the List is not required.
If this is the case then, the simple answer would be to use IReadOnlyList interface:
One doesn't need to use any locking in this situation because there is no way to modify the collection. If in the setter the "_readOnlyList = value;" will be replaced by something more complicated then the lock could be required.
基本上如果你想安全地枚举,你需要使用锁。
这方面请参考MSDN。 http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx
以下是您可能感兴趣的 MSDN 部分:
此类型的公共静态(在 Visual Basic 中为共享)成员是线程安全的。不保证任何实例成员都是线程安全的。
只要集合不被修改,List 就可以同时支持多个读取器。枚举集合本质上不是线程安全的过程。在枚举与一个或多个写访问争用的极少数情况下,确保线程安全的唯一方法是在整个枚举期间锁定集合。要允许多个线程访问集合以进行读写,您必须实现自己的同步。
Basically if you want to enumerate safely, you need to use lock.
Please refer to MSDN on this. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx
Here is part of MSDN that you might be interested:
Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
A List can support multiple readers concurrently, as long as the collection is not modified. Enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with one or more write accesses, the only way to ensure thread safety is to lock the collection during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.
这是没有锁的线程安全列表的类
Here is the class for thread safe list without lock
使用
lock
语句来执行此操作。 (阅读此处了解更多信息。 )仅供参考,这可能并不完全符合您的要求 - 您可能想在代码中进一步锁定,但我不能这么认为。查看
lock
关键字并根据您的具体情况调整其使用。如果需要,您可以使用
_list
变量在get
和set
块中锁定
,这将使因此读/写不能同时发生。Use the
lock
statement to do this. (Read here for more information.)FYI this probably isn't exactly what your asking - you likely want to lock farther out in your code but I can't assume that. Have a look at the
lock
keyword and tailor its use to your specific situation.If you need to, you could
lock
in both theget
andset
block using the_list
variable which would make it so a read/write can not occur at the same time.