如何从ConcurrentBag中删除所有项?

90

如何清空 ConcurrentBag?它没有像 ClearRemoveAll 这样的方法...


1
你可能想阅读这篇文章 https://social.msdn.microsoft.com/Forums/en-US/accf4254-ee81-4059-9251-619bc6bbeadf/clear-a-concurrentqueue?forum=rx - Akira Yamamoto
对于偶然发现本文的读者 - Clear()方法是在.NET Core 2.0中添加的。https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentbag-1.clear?view=net-6.0 - Jon R
7个回答

76

更新于2017年10月3日:正如@Lou所正确指出的,赋值操作是原子性的。在这种情况下,创建ConcurrentBag将不是原子性的,但将该引用放入变量中将是原子性的,因此不严格需要在其周围加锁或使用Interlocked.Exchange

进一步阅读:

引用赋值是原子性的,那么为什么需要Interlocked.Exchange(ref Object, Object)?

引用赋值是线程安全的吗?


您可以始终锁定对包本身的访问并创建其新实例。如果没有其他东西持有它们,那么包中的项目将有资格进行垃圾收集:

lock (something)
{
    bag = new ConcurrentBag();
}

正如Lukazoid所指出的:

var newBag = new ConcurrentBag();
Interlocked.Exchange<ConcurrentBag>(ref bag, newBag);

简单地分组内容,不过这假设每当一个项想要访问它时也会获得锁定,这可能很昂贵,可能会抵消已经投入到 ConcurrentBag 中的性能调整。

如果您知道此时没有其他内容将访问该包,则可以盲目尝试而不锁定 :-)


1
@ChrisMarisic 如果您可以完全避免共享数据,那么您就可以摆脱这些问题。然而,这个问题没有太多的上下文。 - Adam Houldsworth
11
“Interlocked.Exchange”可能比“lock”更好 - Lukazoid
5
如果在进行Interlocked.Exchange时有另一个线程正在往集合里添加元素,那么Interlocked.Exchange会如何工作?在Exchange期间,Add操作是否会被锁定? - Brandon
3
在.NET中,分配任务是原子性的,因此在这里使用锁定和Interlocked.Exchange都是多余的(并且不提供线程安全)。 - Lou
@Adam:唯一的区别在于,如果某种方式下ConcurrentBag构造函数正在改变共享状态,那么这将是任何构造函数的代码异味。移除同步将为您提供更好的吞吐量,即减少争用,因此,如果GC立即收集第二个实例,则无关紧要。在两种情况下,您都无法控制不同的线程是否正在向新集合添加项目,或者已经将其添加到以前的集合中。 - Lou
显示剩余2条评论

41

尽管由于潜在的竞态条件可能不完全清除,但这已经足够了:

while (!myBag.IsEmpty) 
{
   myBag.TryTake(out T _);
}

25
这明显不是原子操作,因此我想不应该将其作为扩展方法添加到ConcurrentBag上,因为所有其他方法都假设具有原子访问。 - Anupam
此外,TryTake 失败的原因除了空之外还有其他原因吗? - BrainSlugs83
24
非常危险。如果另一个进程不断添加项目,这可能会使其保持繁忙状态。 - IvoTops
获取计数,取出计数项并退出会更安全。 - IvoTops
4
@Adam Houldsworth的回答加上我的评论更好。 - Chris Marisic
1
如果这是一个危险的解决方案,为什么它仍然被接受作为答案? - Barış Akkurt

26
所选答案有点像一个解决方法,因此我正在添加自己的解决方法。
我的解决方案是查看System.Collections.Concurrent命名空间中所有可用集合,找到其中一个可以轻松清除集合中所有元素的集合。 ConcurrentStack类具有Clear()方法,该方法从集合中删除所有元素。实际上,它是该命名空间中(目前)唯一一个这样做的集合。是的,您必须使用Push(T element)而不是Add(T element),但坦率地说,这值得节省的时间。

2
然而,这些集合之间还存在许多其他重要的区别。例如,如果您需要确定给定项是否在集合中,使用 Bag 既简单又高效,但使用 Stack 则不是这样。 - Servy
1
@Servy:是的,但仍然如此。实际上,我确实开始写一个优缺点列表,但这真的取决于您的要求是什么。例如,并发集合适用于多线程访问,但它们中没有一个允许您索引到集合中,这可能会阻止您使用它们。问题特别涉及清除并发包(这就是我遇到它的原因),并且我的要求是以线程安全的方式轻松清除集合。我的答案是切换集合。 - user1228
2
我也使用并发堆栈而不是袋子。很奇怪的是,堆栈有Clear而袋子没有。袋子的主要目的是存储值,检查存在性并删除所有或单个值。因此,并发堆栈变成了类似于“有点受限制的真正并发袋子”。 - Maxim
1
@Servy 你如何有效地确定一个给定项是否包含在ConcurrentBag中?我看不到任何本地属性或方法可以做到这一点。Contains不算。它是通用IEnumerable的扩展方法,而且一点也不高效。 - Theodor Zoulias

13
自.NET Core 2.0 / .NET Standard 2.1 / .NET Framework 5.0开始,ConcurrentBag<T>上有一个Clear()方法。请参见:ConcurrentBag.Clear

9

在解决问题的精神下... ConcurrentDictionary<T, bool> 具有原子清除功能,同时允许您快速检查键是否存在。当然,“快速”是一个相对的术语,但根据您的使用情况,它可能比枚举大型堆栈要快。


不错!这应该被视为为此类实例选择的容器类型。同样适用于ConcurrentStack。 - Latency

-1

嗯,我总是认为让底层框架去做工作更好。我只需将逻辑封装在一个专用函数中,在函数调用后,所有本地变量都会在GC感觉必要时自动丢弃。

void MyMainFunction(){
       DoWorkWithTheBag();
}
  
void DoWorkWithTheBag(){
      var newBag = new ConcurrentBag();
      .....
} 

如果您想强制进行垃圾回收,您也可以调用GC.Collect。

-3
int cnt = _queue.Count;
for (; cnt > 0; cnt--)
{
     _queue.TryDequeue(out img);
}

它不会陷入无限循环,并清除当前时间的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接