我有一个方法,它采用IList <>并添加东西.在某些情况下,我想将它传递给ConcurrentBag,但它不实现IList <>或ICollection <>,只实现非通用的ICollection,它没有Add方法.
现在,我明白为什么它不能(可能)实现IList - 它不是一个有序的集合,所以它有一个索引器没有意义.但我没有看到任何ICollection <>方法的问题.
所以为什么?而且,在.NET中是否有一个线程安全的集合可以实现更强大的接口?
Ric*_*key 34
A List<T>不是并发的,所以它可以实现ICollection<T>它给你一对方法Contains和Add.如果Contains返回,false您可以安全地打电话,Add知道它会成功.
A ConcurrentBag<T>是并发的,因此无法实现,ICollection<T>因为在Contains您调用时,答案返回可能无效Add.相反,它实现IProducerConsumerCollection<T>它提供了一个方法TryAdd,做双方的工作Contains和Add.
所以不幸的是,你希望对两个集合进行操作,但两个集合都没有共享接口.有很多方法可以解决这个问题,但是当API与这些问题类似时,我首选的方法是为两个接口提供方法重载,然后使用lambda表达式来创建使用自己的方法为每个接口执行相同操作的委托.然后,您可以使用该委托代替您执行几乎常见操作的位置.
这是一个简单的例子:
public class Processor
{
/// <summary>
/// Process a traditional collection.
/// </summary>
/// <param name="collection">The collection.</param>
public void Process(ICollection<string> collection)
{
Process(item =>
{
if (collection.Contains(item))
return false;
collection.Add(item);
return true;
});
}
/// <summary>
/// Process a concurrent collection.
/// </summary>
/// <param name="collection">The collection.</param>
public void Process(IProducerConsumerCollection<string> collection)
{
Process(item => collection.TryAdd(item));
}
/// <summary>
/// Common processing.
/// </summary>
/// <param name="addFunc">A func to add the item to a collection</param>
private void Process(Func<string, bool> addFunc)
{
var item = "new item";
if (!addFunc(item))
throw new InvalidOperationException("duplicate item");
}
}
Run Code Online (Sandbox Code Playgroud)
Bra*_*ger 11
这并不是说ConcurrentBag<T> 无法实现ICollection<T>; 您可以想象Contains可以使用TryPeek或Remove使用TryTake.
问题在于将a ConcurrentBag<T>视为一个ICollection<T>(例如,通过允许在传递ConcurrentBag<T>到仅采用的方法时进行隐式转换ICollection<T>)是不明智的,因为大多数消费者ICollection<T>期望它具有截然不同的语义ConcurrentBag<T>.
ICollection<T>以参数作为参数的大多数方法都可能做出假设(在单线程场景中是安全的),例如" Add后跟Contains将始终返回true",或"如果Contains返回true,那么将Remove".但是,在高度多线程的情况下(这可能是首先使用ConcurrentBag<T>的情况),这些假设极不可能成立.这可能会暴露代码中的错误,这些错误是在假设ICollection<T>在单线程场景中使用时编写的.
如果你真的需要公开ConcurrentBag<T>为ICollection<T>(并且你知道你传递给它的代码期望它以非ICollection<T>方式工作),编写一个包装类(使用适配器模式)应该相当简单模拟ICollection<T>使用最接近的可用方法的方法ConcurrentBag<T>.
为什么不
ConcurrentBag<T>实施ICollection<T>?
因为它不能。具体来说,该方法的功能ICollection<T>.Remove不受ConcurrentBag<T>. 您无法从此集合中删除特定项目。你只能“拿走”一件物品,并且由收藏本身决定给你哪件物品。
这ConcurrentBag<T>是一个专门的集合,旨在支持特定场景(混合生产者-消费者场景,主要是对象池)。其内部结构的选择是为了最佳地支持这些场景。每个线程在内部维护ConcurrentBag<T>一个WorkStealingQueue(内部类)。项目始终被推入当前线程队列的尾部。项目将从当前线程队列的尾部弹出,除非其为空,在这种情况下,项目会从另一个线程队列的头部“窃取”。从本地队列中推送和弹出是无锁的。这就是该集合设计的最佳用途:从本地缓冲区存储和检索项目,而不与其他线程争用锁。编写这样的无锁代码非常困难。如果你看到这个类的源代码,你一定会大吃一惊。如果允许另一个线程从 中的任何位置WorkStealingQueue(而不仅仅是头部)窃取项目,那么这个核心功能是否可以保持无锁状态?我不知道这个问题的答案,但如果我不得不猜测,根据方法中的以下评论,WorkStealingQueue.TryLocalPeek我会说不:
// It is possible to enable lock-free peeks, following the same general approach
// that's used in TryLocalPop. However, peeks are more complicated as we can't
// do the same kind of index reservation that's done in TryLocalPop; doing so could
// end up making a steal think that no item is available, even when one is. To do
// it correctly, then, we'd need to add spinning to TrySteal in case of a concurrent
// peek happening. With a lock, the common case (no contention with steals) will
// effectively only incur two interlocked operations (entering/exiting the lock) instead
// of one (setting Peek as the _currentOp). Combined with Peeks on a bag being rare,
// for now we'll use the simpler/safer code.
Run Code Online (Sandbox Code Playgroud)
所以TryPeek使用 a lock,并不是因为使其无锁是不可能的,而是因为它很难。想象一下,如果可以从队列中的任意位置删除项目会有多困难。而Remove功能正是需要这样的。
| 归档时间: |
|
| 查看次数: |
7147 次 |
| 最近记录: |