除非您确定线程的访问模式,否则不要使用ConcurrentBag<T> 替换锁定的List<T>,因为它在后台使用线程本地存储。
MSDN 谈论首选用法:
“ConcurrentBag<T> 是一个线程安全的包实现,针对同一线程将同时生产和使用包中存储的数据的场景进行了优化。”
同样重要的是要注意List<T> 是有序 而ConcurrentBag<T> 是无序。如果您不关心收藏中的顺序,我会使用ConcurrentQueue<T>。
关于性能,下面是来自ConcurrentBag<T> 的一些代码。但是要考虑的主要事情是,如果您执行 Take 并且您的线程本地存储为空,它将从其他线程窃取,这很昂贵。
当它需要窃取时,注意它是锁定的。另请注意,它可以多次锁定一个Take,因为TrySteal 可能会失败并从Steal 多次调用(未显示)。
private bool TrySteal(ConcurrentBag<T>.ThreadLocalList list, out T result, bool take)
{
lock (list)
{
if (this.CanSteal(list))
{
list.Steal(out result, take);
return true;
}
result = default (T);
return false;
}
}
CanSteal 期间也可能有旋转等待。
private bool CanSteal(ConcurrentBag<T>.ThreadLocalList list)
{
if (list.Count <= 2 && list.m_currentOp != 0)
{
SpinWait spinWait = new SpinWait();
while (list.m_currentOp != 0)
spinWait.SpinOnce();
}
return list.Count > 0;
}
最后,即使添加也可能导致锁定。
private void AddInternal(ConcurrentBag<T>.ThreadLocalList list, T item)
{
bool lockTaken = false;
try
{
Interlocked.Exchange(ref list.m_currentOp, 1);
if (list.Count < 2 || this.m_needSync)
{
list.m_currentOp = 0;
Monitor.Enter((object) list, ref lockTaken);
}
list.Add(item, lockTaken);
}
finally
{
list.m_currentOp = 0;
if (lockTaken)
Monitor.Exit((object) list);
}
}