【问题标题】:Why and how does a foreach-loop on BlockingCollection run infinite?BlockingCollection 上的 foreach 循环为什么以及如何无限运行?
【发布时间】:2020-06-10 17:56:23
【问题描述】:

我正在开发一个简单的文件记录器,遇到了线程安全问题,并且正在研究其他人是如何做到的。 我遇到了使用 BlockingCollection 作为队列和 foreach 循环来处理该队列的方法:

var queue = new BlockingCollection<string>(1024);
var t = Task.Factory.StartNew(() => {
                                        foreach (var message in queue.GetConsumingEnumerable()) {
                                            WriteMessageToFile(message);
                                        }
                                    }, TaskCreationOptions.LongRunning);

Dim queue = New BlockingCollection(Of String)(1024)
Dim t = Task.Factory.StartNew(Sub()
                                  For Each message In queue.GetConsumingEnumerable()
                                      WriteMessageToFile(message)
                                  Next
                              End Sub, TaskCreationOptions.LongRunning)

这个 For Each 循环实际上是无限运行的。它处理我添加到 queue 的数据,直到我在 BlockingCollection 上调用 .CompleteAdding

我想知道为什么会这样,以及如何以及是否是一个好方法。 集合为空时线程会做什么,它会检查每个刻度吗?这不是资源繁重吗?

【问题讨论】:

  • 如果生产者没有明确表示不会再有数据,您将如何期望消费可枚举来区分“集合当前为空但可能会改变”之间的区别(因此,继续枚举)和“集合当前是空的,永远不会改变”(因此,放弃枚举)?它们(至少)是两个独立的线程/任务,因此一个需要以某种方式将其传达给另一个。
  • 是的,这是一个好方法。不,它不会检查每一个刻度;它确实阻塞了一个线程,但是当集合为空时,O/S 不会安排该线程执行。
  • @Fox 它是在阻塞集合中根据 Windows 线程原语实现的逻辑。我推测(不检查源代码)它告诉 Windows 等待由 add 例程触发的内核事件(与 .NET 事件不同)。
  • 再次,想想消耗的数据比产生的数据快。当消费者“赶上”生产者时,集合可能会变空。仅仅因为没有更多数据需要处理现在并不意味着不会在未来,所以枚举器需要保持“活跃”如果以及何时发生这种情况。 GetConsumingEnumerator() 启用此foreach 用法,除其他外,仅在BlockingCollection&lt;&gt;.IsCompletedtrue 时终止枚举(意味着Count0 CompleteAdding() 已被调用) .请记住,它支持多个生产者或消费者。
  • 关于For Each 发生的事情,幕后发生的事情是调用枚举器的MoveNext 方法(前进并检查是否到达终点)并读取Current财产。在ConsumingIterator 的情况下,我希望MoveNext 只是阻塞,直到它有东西(或什么都没有)要返回。

标签: c# .net vb.net foreach blockingcollection


【解决方案1】:

它使用SemiphoreSlim 等待GetConsumingEnumerable,并在添加项目时调用Release(高度简化)。

SemaphoreSlim 是 Semaphore 类的轻量级替代方案,它 不使用 Windows 内核信号量

https://docs.microsoft.com/en-us/dotnet/api/system.threading.semaphoreslim?view=netcore-3.1

您可以在此处阅读BlockingCollection 的完整代码:https://github.com/dotnet/runtime/blob/4f9ae42d861fcb4be2fcd5d3d55d5f227d30e723/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/BlockingCollection.cs

【讨论】:

  • 感谢来源!连同@Craig 关于 foreach 实际工作原理的信息,我现在已经理解了。
猜你喜欢
  • 2021-10-28
  • 2019-10-23
  • 2021-09-03
  • 1970-01-01
  • 1970-01-01
  • 2018-10-09
  • 1970-01-01
  • 1970-01-01
  • 2012-09-02
相关资源
最近更新 更多