【发布时间】: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<>.IsCompleted为true时终止枚举(意味着Count为0和CompleteAdding()已被调用) .请记住,它支持多个生产者或消费者。 -
关于
For Each发生的事情,幕后发生的事情是调用枚举器的MoveNext方法(前进并检查是否到达终点)并读取Current财产。在ConsumingIterator的情况下,我希望MoveNext只是阻塞,直到它有东西(或什么都没有)要返回。
标签: c# .net vb.net foreach blockingcollection