简体   繁体   English

TPL Dataflow,我能否查询一个数据块是否标记为完成但尚未完成?

[英]TPL Dataflow, can I query whether a data block is marked complete but has not yet completed?

Given the following:鉴于以下情况:

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
});

The targetBlock never seems to complete and I think the reason is that all the items in the TransformBlock targetBlock are waiting in the output queue as I have not linked the targetBlock to any other Dataflow block. targetBlock似乎永远不会完成,我认为原因是TransformBlock targetBlock中的所有项目都在输出队列中等待,因为我没有将targetBlock链接到任何其他数据流块。 However, what I actually want to achieve is a notification when (A) the targetBlock is notified of completion AND (B) the input queue is empty.但是,我真正想要实现的是在 (A) 通知targetBlock完成并且 (B) 输入队列为空时发出通知。 I do not want to care whether items still sit in the output queue of the TransformBlock .我不想关心项目是否仍然位于TransformBlock的输出队列中。 How can I go about that?我该怎么做? Is the only way to get what I want to query the completion status of the sourceBlock AND to make sure the InputCount of the targetBlock is zero?获取我想要查询sourceBlock的完成状态并确保InputCounttargetBlock为零的唯一方法是什么? I am not sure this is very stable (is the sourceBlock truly only marked completed if the last item in the sourceBlock has been passed to the targetBlock ?).我不确定这是否非常稳定(如果sourceBlock中的最后一项已传递给targetBlocksourceBlock是否真的只标记为已完成?)。 Is there a more elegant and more efficient way to get to the same goal?是否有更优雅、更有效的方法来达到相同的目标?

Edit: I just noticed even the "dirty" way to check on completion of the sourceBlock AND InputCount of the targetBlock being zero is not trivial to implement.编辑:我刚刚注意到即使是检查sourceBlock的完成和InputCounttargetBlock是否为零的“肮脏”方法也不是很容易实现。 Where would that block sit?那个街区会坐在哪里? It cannot be within the targetBlock because once above two conditions are met obviously no message is processed within targetBlock anymore.它不能在targetBlock内,因为一旦满足上述两个条件,显然不再有消息在targetBlock内处理。 Also checking on the completion status of the sourceBlock introduces a lot of inefficiency.同时检查sourceBlock的完成状态会导致很多效率低下。

I believe you can't directly do this.我相信你不能直接这样做。 It's possible you could get this information from some private fields using reflection, but I wouldn't recommend doing that.您可以使用反射从一些private字段中获取此信息,但我不建议这样做。

But you can do this by creating custom blocks.但是您可以通过创建自定义块来做到这一点。 In the case of Complete() it's simple: just create a block that forwards each method to the original block.Complete()的情况下很简单:只需创建一个将每个方法转发到原始块的块。 Except Complete() , where it will also log it.除了Complete() ,它也会记录它。

In the case of figuring out when processing of all items is complete, you could link your block to an intermediate BufferBlock .在确定所有项目的处理何时完成的情况下,您可以将您的块链接到中间BufferBlock This way, the output queue will be emptied quickly and so checking Completed of the internal block would give you fairly accurate measurement of when the processing is complete.这样,输出队列将被快速清空,因此检查内部块的Completed可以相当准确地测量处理何时完成。 This would affect your measurements, but hopefully not significantly.这会影响您的测量,但希望影响不大。

Another option would be to add some logging at the end of the block's delegate.另一种选择是在块委托的末尾添加一些日志记录。 This way, you could see when processing of the last item was finished.这样,您可以看到最后一项的处理何时完成。

It would be nice if the TransformBlock had a ProcessingCompleted event that would fire when the block has completed the processing of all messages in its queue, but there is no such event.如果TransformBlock有一个ProcessingCompleted事件,该事件将在块完成其队列中所有消息的处理时触发,但没有这样的事件,那就太好了。 Below is an attempt to rectify this omission.以下是纠正此遗漏的尝试。 The CreateTransformBlockEx method accepts an Action<Exception> handler, that is invoked when this "event" occurs. CreateTransformBlockEx方法接受一个Action<Exception>处理程序,当此“事件”发生时将调用该处理程序。

The intention was to always invoke the handler before the final completion of the block.目的是始终在块最终完成之前调用处理程序。 Unfortunately in the case that the supplied CancellationToken is canceled, the completion (cancellation) happens first, and the handler is invoked some milliseconds later.不幸的是,在取消提供的CancellationToken的情况下,完成(取消)首先发生,然后在几毫秒后调用处理程序。 To fix this inconsistency would require some tricky workarounds, and may had other unwanted side-effects, so I am leaving it as is.要解决这种不一致需要一些棘手的解决方法,并且可能会产生其他不需要的副作用,所以我将其保留原样。

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);
}

The code of the local function PropagateCompletion mimics the source code of the LinkTo built-in method, when invoked with the PropagateCompletion = true option.当使用PropagateCompletion = true选项调用时,本地函数PropagateCompletion的代码模仿LinkTo内置方法的源代码

Usage example:使用示例:

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
});

First thing it is not right to use a IPropagator Block as a leaf terminal.首先,将 IPropagator 块用作叶终端是不正确的。 But still your requirement can be fulfilled by asynchronously checking the output buffer of the TargetBlock for output messages and then consuming then so that the buffer could be emptied.但是仍然可以通过异步检查 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
        });

` `

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM