簡體   English   中英

TPL Dataflow,我能否查詢一個數據塊是否標記為完成但尚未完成?

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

鑒於以下情況:

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

targetBlock似乎永遠不會完成,我認為原因是TransformBlock targetBlock中的所有項目都在輸出隊列中等待,因為我沒有將targetBlock鏈接到任何其他數據流塊。 但是,我真正想要實現的是在 (A) 通知targetBlock完成並且 (B) 輸入隊列為空時發出通知。 我不想關心項目是否仍然位於TransformBlock的輸出隊列中。 我該怎么做? 獲取我想要查詢sourceBlock的完成狀態並確保InputCounttargetBlock為零的唯一方法是什么? 我不確定這是否非常穩定(如果sourceBlock中的最后一項已傳遞給targetBlocksourceBlock是否真的只標記為已完成?)。 是否有更優雅、更有效的方法來達到相同的目標?

編輯:我剛剛注意到即使是檢查sourceBlock的完成和InputCounttargetBlock是否為零的“骯臟”方法也不是很容易實現。 那個街區會坐在哪里? 它不能在targetBlock內,因為一旦滿足上述兩個條件,顯然不再有消息在targetBlock內處理。 同時檢查sourceBlock的完成狀態會導致很多效率低下。

我相信你不能直接這樣做。 您可以使用反射從一些private字段中獲取此信息,但我不建議這樣做。

但是您可以通過創建自定義塊來做到這一點。 Complete()的情況下很簡單:只需創建一個將每個方法轉發到原始塊的塊。 除了Complete() ,它也會記錄它。

在確定所有項目的處理何時完成的情況下,您可以將您的塊鏈接到中間BufferBlock 這樣,輸出隊列將被快速清空,因此檢查內部塊的Completed可以相當准確地測量處理何時完成。 這會影響您的測量,但希望影響不大。

另一種選擇是在塊委托的末尾添加一些日志記錄。 這樣,您可以看到最后一項的處理何時完成。

如果TransformBlock有一個ProcessingCompleted事件,該事件將在塊完成其隊列中所有消息的處理時觸發,但沒有這樣的事件,那就太好了。 以下是糾正此遺漏的嘗試。 CreateTransformBlockEx方法接受一個Action<Exception>處理程序,當此“事件”發生時將調用該處理程序。

目的是始終在塊最終完成之前調用處理程序。 不幸的是,在取消提供的CancellationToken的情況下,完成(取消)首先發生,然后在幾毫秒后調用處理程序。 要解決這種不一致需要一些棘手的解決方法,並且可能會產生其他不需要的副作用,所以我將其保留原樣。

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

當使用PropagateCompletion = true選項調用時,本地函數PropagateCompletion的代碼模仿LinkTo內置方法的源代碼

使用示例:

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

首先,將 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
        });

`

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM