我有一个生产者将数据发送到BufferBlock
,并且从源中读取了所有数据后,它将调用Complete()
。
默认行为是,当调用完成时,即使缓冲区中仍然有消息,它也会沿管道传播完成。
是否需要等待告诉一个块:仅在缓冲区为空时才传播完成?
完成时,我在Receive
上遇到了异常:InvalidOperationException: 'The source completed without providing data to receive.'
我当前正在使用:
var bufferBlock = new BufferBlock<string>();
var transformBlock = new TransformBlock<string, string>(s =>
{
Thread.Sleep(50);
return s;
});
bufferBlock.LinkTo(transformBlock, new DataflowLinkOptions { PropagateCompletion = true });
foreach (var i in Enumerable.Range(0, 10))
bufferBlock.Post(i.ToString());
bufferBlock.Complete();
while (!transformBlock.Completion.IsCompleted)
Console.WriteLine(transformBlock.Receive());
为避免这种情况,我目前正在使用:
while (bufferBlock.Count > 0)
await Task.Delay(100);
bufferBlock.Complete();
听起来不是一个很干净的解决方案。
这是比赛条件吗?即阻止标记为未完成,并且在我打电话时它们已完成接收?
我想我可以将!transformBlock.Completion.IsCompleted
替换为block.OutputAvailableAsync
,对吗?
答案 0 :(得分:0)
要等待管道完成,您应该等待管道中最后一个块的完成任务。在这种情况下,您应该将代码更改为:
foreach (var i in Enumerable.Range(0, 10))
bufferBlock.Post(i.ToString());
bufferBlock.Complete();
await transformBlock.Completion;
这在Completing a pipeline的Waiting for the pipeline to finish和Walkthrough: Creating a Dataflow Pipeline段落中得到证明
TransformBlock已经有一个缓冲区,这意味着发布到输入BufferBlock的所有内容都将立即发送到TransformBlock。最好将其他块用于测试目的。本演练演示了一个很好的示例:一个transformBlock用于下载页面内容,另一个用于解析页面内容,等等。
请小心各种不幸的编码实践,例如每次创建一个新的HttpClient实例。下载器可以更改为:
var httpClient=new HttpClient();
var downloadString = new TransformBlock<string, string>(async uri =>
{
Console.WriteLine("Downloading '{0}'...", uri);
return await httpClient.GetStringAsync(uri);
});
答案 1 :(得分:0)
是的,手动从块中检索消息的正确方法是结合使用OutputAvailableAsync
和TryReceive
:
while (await transformBlock.OutputAvailableAsync())
{
while (transformBlock.TryReceive(out var item))
{
Console.WriteLine(item);
}
}
await transformBlock.Completion; // Required to propagate exceptions
属性BufferBlock.Count
,TransformBlock.OutputCount
等仅适用于监视和统计。在大多数情况下,使用它们来控制数据流是可能出现争用状况和潜在错误的指示。