为什么 ConcurrentBag 不支持实现 ICollection?
我有一个方法,它采用 IList
并向其中添加内容。我想传递一个 ConcurrentBag
在某些情况下,但它不实现 IList
或 ICollection
,仅实现非通用 ICollection
,它没有 Add
方法。
现在,我明白为什么它不能(也许)实现 IList
- 它不是一个有序集合,因此它拥有索引器是没有意义的。但我没有发现任何 ICollection
方法。
那么,为什么呢?而且,.NET 中是否有线程安全的集合可以实现更健壮的接口?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
List
不是并发的,因此它可以实现ICollection
,它为您提供了一对方法Contains
和添加
。如果Contains
返回false
,您可以安全地调用Add
,知道它会成功。ConcurrentBag
是并发的,因此它无法实现ICollection
,因为Contains
返回的答案在您调用时可能无效添加
。相反,它实现了IProducerConsumerCollection
,它提供了单一方法TryAdd
,该方法可以完成Contains
和Add
的工作。 。因此,不幸的是,您希望对两个都是集合但不共享公共接口的事物进行操作。有很多方法可以解决这个问题,但当 API 与这些相似时,我的首选方法是为两个接口提供方法重载,然后使用 lambda 表达式来制作委托,这些委托使用各自的方法为每个接口执行相同的操作。然后,您可以使用该委托来代替执行几乎常见操作的位置。
这是一个简单的例子:
A
List<T>
is not concurrent and so it can implementICollection<T>
which gives you the pair of methodsContains
andAdd
. IfContains
returnsfalse
you can safely callAdd
knowing it will succeed.A
ConcurrentBag<T>
is concurrent and so it cannot implementICollection<T>
because the answerContains
returns might be invalid by the time you callAdd
. Instead it implementsIProducerConsumerCollection<T>
which provides the single methodTryAdd
that does the work of bothContains
andAdd
.So unfortunately you desire to operate on two things that are both collections but don't share a common interface. There are many ways to solve this problem but my preferred approach when the API is as similar as these are is to provide method overloads for both interfaces and then use lambda expressions to craft delegates that perform the same operation for each interface using their own methods. Then you can use that delegate in place of where you would have performed the almost common operation.
Here's a simple example:
有
SynchronizedCollection
,实现了IList< T>
和ICollection
以及IEnumerable
。There's
SynchronizedCollection<T>
, implements bothIList<T>
andICollection<T>
as well asIEnumerable<T>
.因为它不能。具体来说,该方法的功能.Remove 。您无法从此集合中删除特定项目。您只能“采取” 一个项目,由集合本身决定 哪一项给你。
ConcurrentBag
不支持 >ICollectionConcurrentBag
是一个专门的集合,旨在支持特定场景(混合生产者-消费者场景,主要是 对象池)。其内部结构的选择是为了最佳地支持这些场景。ConcurrentBag
在内部维护一个WorkStealingQueue
(内部类)。项目始终被推入当前线程队列的尾部。项目将从当前线程队列的尾部弹出,除非其为空,在这种情况下,项目会从另一个线程队列的头部“窃取”。从本地队列中推送和弹出是无锁的。这就是该集合设计的最佳用途:从本地缓冲区存储和检索项目,而不与其他线程争用锁。编写这样的无锁代码非常困难。如果您看到 此类的源代码,它会让你大吃一惊。如果允许另一个线程从 WorkStealingQueue 中的任何位置(而不仅仅是头部)窃取项目,那么此核心功能是否可以保持无锁状态?我不知道答案,但如果我不得不猜测,基于WorkStealingQueue.TryLocalPeek
方法我会说不:所以 < a href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentbag-1.trypeek" rel="nofollow noreferrer">
TryPeek
< /a> 使用锁
,不是因为使其无锁是不可能的,而是因为它很难。想象一下,如果可以从队列中的任意位置删除项目会有多困难。Remove
功能正是需要这样的功能。Because it can't. Specifically the functionality of the method
ICollection<T>.Remove
is not supported by theConcurrentBag<T>
. You can't remove a specific item from this collection. You can only "take" an item, and it's up to the collection itself to decide which item to give you.The
ConcurrentBag<T>
is a specialized collection intended to support specific scenarios (mixed producer-consumer scenarios, mainly object pools). Its internal structure was chosen to support optimally these scenarios. TheConcurrentBag<T>
maintains internally oneWorkStealingQueue
(internal class) per thread. Items are always pushed in the tail of the current thread's queue. Items are popped from the tail of the current thread's queue, unless its empty, in which case an item is "stolen" from the head of another thread's queue. Pushing and popping from the local queue is lock-free. That's what this collection was designed to do best: to store and retrieve items from a local buffer, without contending for locks with other threads. Writing lock-free code like this is extremely hard. If you see the source code of this class, it will blow your mind. Could this core functionality stay lock-free if another thread was allowed to steal an item from any place in theWorkStealingQueue
, not just the head? I don't know the answer to this, but if I had to guess, based on the following comment in theWorkStealingQueue.TryLocalPeek
method I'd say no:So the
TryPeek
uses alock
, not because making it lock-free is impossible but because it is hard. Imagine how harder it would be if items could be removed from arbitrary places inside the queue. And theRemove
functionality would require exactly that....
提供某种 ICollection 功能。
...
Giving a sorts of ICollection capability.