C# TPL数据流,我可以查询数据块是否标记为已完成但尚未完成吗?
鉴于以下情况:C# TPL数据流,我可以查询数据块是否标记为已完成但尚未完成吗?,c#,concurrency,task-parallel-library,message-passing,tpl-dataflow,C#,Concurrency,Task Parallel Library,Message Passing,Tpl Dataflow,鉴于以下情况: BufferBlock<int> sourceBlock = new BufferBlock<int>(); TransformBlock<int, int> targetBlock = new TransformBlock<int, int>(element => { return element * 2; }); sourceBlock.LinkTo(targetBlock, new DataflowLinkOp
BufferBlock<int> sourceBlock = new BufferBlock<int>();
TransformBlock<int, int> targetBlock = new TransformBlock<int, int>(element =>
{
return element * 2;
});
sourceBlock.LinkTo(targetBlock, new DataflowLinkOptions { PropagateCompletion = true });
//feed some elements into the buffer block
for(int i = 1; i <= 1000000; i++)
{
sourceBlock.SendAsync(i);
}
sourceBlock.Complete();
targetBlock.Completion.ContinueWith(_ =>
{
//notify completion of the target block
});
BufferBlock sourceBlock=new BufferBlock();
TransformBlock targetBlock=新TransformBlock(元素=>
{
返回元素*2;
});
LinkTo(targetBlock,新数据流链接选项{PropagateCompletion=true});
//将一些元素输入缓冲块
对于(int i=1;i
{
//通知目标区块的完成
});
targetBlock
似乎从未完成,我认为原因是TransformBlock
targetBlock
中的所有项目都在输出队列中等待,因为我没有将targetBlock
链接到任何其他数据流块。然而,我实际想要实现的是当(a)通知targetBlock
完成时的通知,以及(B)输入队列为空时的通知。我不想关心项目是否仍然位于TransformBlock
的输出队列中。我该怎么做呢?获取我想要查询的sourceBlock
的完成状态并确保targetBlock
的InputCount
为零的唯一方法是什么?我不确定这是否非常稳定(只有当sourceBlock
中的最后一项已传递给targetBlock
时,sourceBlock
才真正标记为已完成)。有没有一种更优雅、更有效的方法来达到同样的目标
编辑:我刚刚注意到,即使是检查
sourceBlock
和targetBlock
的InputCount
是否为零的“脏”方法也不容易实现。那个街区在哪里?它不能在targetBlock
内,因为一旦满足上述两个条件,显然targetBlock
内不再处理任何消息。同时,检查源代码块的完成状态也会带来很多低效。我相信您不能直接这样做。您可以使用反射从一些private
字段获取此信息,但我不建议这样做
但可以通过创建自定义块来实现这一点。在Complete()
的情况下,很简单:只需创建一个块,将每个方法转发到原始块。除了Complete()
,它还将记录它
在计算所有项目的处理何时完成的情况下,您可以将块链接到中间缓冲块。这样,输出队列将快速清空,因此检查内部块的Completed
,可以相当准确地测量处理何时完成。这会影响您的测量,但希望不会太大
另一个选项是在块的委托末尾添加一些日志记录。这样,您可以看到最后一项的处理何时完成。首先,使用IPropagator块作为叶终端是不正确的。但是仍然可以通过异步检查TargetBlock的输出缓冲区中是否有输出消息,然后使用这些消息,从而清空缓冲区,从而满足您的需求
` BufferBlock<int> sourceBlock = new BufferBlock<int>();
TransformBlock<int, int> targetBlock = new TransformBlock<int, int>
(element =>
{
return element * 2;
});
sourceBlock.LinkTo(targetBlock, new DataflowLinkOptions {
PropagateCompletion = true });
//feed some elements into the buffer block
for (int i = 1; i <= 100; i++)
{
sourceBlock.SendAsync(i);
}
sourceBlock.Complete();
bool isOutputAvailable = await targetBlock.OutputAvailableAsync();
while(isOutputAvailable)
{
int value = await targetBlock.ReceiveAsync();
isOutputAvailable = await targetBlock.OutputAvailableAsync();
}
await targetBlock.Completion.ContinueWith(_ =>
{
Console.WriteLine("Target Block Completed");//notify completion of the target block
});
`BufferBlock sourceBlock=new BufferBlock();
TransformBlock targetBlock=新TransformBlock
(元素=>
{
返回元素*2;
});
LinkTo(targetBlock,新数据流链接选项{
完成=真});
//将一些元素输入缓冲块
对于(int i=1;i
{
Console.WriteLine(“目标块已完成”);//通知目标块的完成
});
`如果TransformBlock
有一个ProcessingCompleted
事件,该事件将在块已完成其队列中所有消息的处理时触发,但不存在此类事件,那就太好了。以下是纠正这一遗漏的尝试。CreateTransformBlockEx
方法接受一个操作
处理程序,该处理程序在发生此“事件”时被调用
其目的是始终在块最终完成之前调用处理程序。不幸的是,在所提供的被取消的情况下,完成(取消)首先发生,并且处理程序在几毫秒后被调用。要解决这种不一致性,需要一些棘手的解决办法,并且可能会产生其他不必要的副作用,所以我将保持原样
public static IPropagatorBlock<TInput, TOutput>
CreateTransformBlockEx<TInput, TOutput>(Func<TInput, Task<TOutput>> transform,
Action<Exception> onProcessingCompleted,
ExecutionDataflowBlockOptions dataflowBlockOptions = null)
{
if (onProcessingCompleted == null)
throw new ArgumentNullException(nameof(onProcessingCompleted));
dataflowBlockOptions = dataflowBlockOptions ?? new ExecutionDataflowBlockOptions();
var transformBlock = new TransformBlock<TInput, TOutput>(transform,
dataflowBlockOptions);
var bufferBlock = new BufferBlock<TOutput>(dataflowBlockOptions);
transformBlock.LinkTo(bufferBlock);
PropagateCompletion(transformBlock, bufferBlock, onProcessingCompleted);
return DataflowBlock.Encapsulate(transformBlock, bufferBlock);
async void PropagateCompletion(IDataflowBlock block1, IDataflowBlock block2,
Action<Exception> completionHandler)
{
try
{
await block1.Completion.ConfigureAwait(false);
}
catch { }
var exception =
block1.Completion.IsFaulted ? block1.Completion.Exception : null;
try
{
// Invoke the handler before completing the second block
completionHandler(exception);
}
finally
{
if (exception != null) block2.Fault(exception); else block2.Complete();
}
}
}
// Overload with synchronous lambda
public static IPropagatorBlock<TInput, TOutput>
CreateTransformBlockEx<TInput, TOutput>(Func<TInput, TOutput> transform,
Action<Exception> onProcessingCompleted,
ExecutionDataflowBlockOptions dataflowBlockOptions = null)
{
return CreateTransformBlockEx<TInput, TOutput>(
x => Task.FromResult(transform(x)), onProcessingCompleted,
dataflowBlockOptions);
}
为什么您需要知道这一点?因为一旦targetBlock中的所有项目完成处理,即使项目仍在outQueue中,我也会完成一个流程。一个原因是该过程的完成,另一个原因是对延迟、吞吐量和性能的测量。链接的下一个数据块可能需要更长的时间来处理OutQueue中的所有剩余项为什么不等待源块的任务?您不需要将块附加到该块,只需要源块的Completion
属性上的一个延续。如果您想清空TransformBlock
的缓冲区,您可以将其链接到如下所示的:targetBlock.LinkTo(DataflowBlock.NullTarget())代码>
var httpClient = new HttpClient();
var downloader = CreateTransformBlockEx<string, string>(async url =>
{
return await httpClient.GetStringAsync(url);
}, onProcessingCompleted: ex =>
{
Console.WriteLine($"Download completed {(ex == null ? "OK" : "Error")}");
}, new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = 10
});