简体   繁体   English

TPL Dataflow:如何在当前尚未完成时开始下一个异步操作,保留执行顺序?

[英]TPL Dataflow: How to start the next async action when the current one hasn't finished yet, preserving the execution order?

Consider the following program, which uses TPL Dataflow.考虑以下使用 TPL 数据流的程序。 Hence, ActionBlock comes from the Dataflow library.因此, ActionBlock来自 Dataflow 库。

internal static class Program
{
    public static async Task Main(string[] args)
    {
        var actionBlock = new ActionBlock<int>(async i =>
        {
            Console.WriteLine($"Started with {i}");
            await DoSomethingAsync(i);
            Console.WriteLine($"Done with {i}");
        });

        for (int i = 0; i < 5; i++)
        {
            actionBlock.Post(i);
        }

        actionBlock.Complete();
        await actionBlock.Completion;
    }

    private static async Task DoSomethingAsync(int i)
    {
        await Task.Delay(1000);
    }
}

The output of this program is:该程序的output为:

Started with 0
Done with 0
Started with 1
Done with 1
Started with 2
Done with 2
Started with 3
Done with 3
Started with 4
Done with 4

Reason is that the ActionBlock only starts processing the next task when the previous asynynchronous task was finished.原因是ActionBlock只有在前一个异步任务完成后才开始处理下一个任务。

How can I force it to start processing the next task, even though the previous wasn't fully finished.我如何强制它开始处理下一个任务,即使前一个任务没有完全完成。 MaxDegreeOfParallelism isn't an option, as that can mess up the order. MaxDegreeOfParallelism不是一个选项,因为这会打乱顺序。

So I'd like the output to be:所以我希望 output 是:

Started with 0
Started with 1
Started with 2
Started with 3
Started with 4
Done with 0
Done with 1
Done with 2
Done with 3
Done with 4

I could get rid of the async/await and replace it with ContinueWith .我可以摆脱async/await并用ContinueWith替换它。 But that has two disadvantages:但这有两个缺点:

  1. The ActionBlock think it's done with the message immediately. ActionBlock 认为它立即完成了消息。 An optional call to Complete() would result in the pipeline being completed directly, instead of after the asynchronous action to be completed.Complete()的可选调用将导致管道直接完成,而不是在异步操作完成之后。
  2. I'd like to add a BoundedCapacity to limit the amount of messages currently still waiting to be fully finished.我想添加一个BoundedCapacity来限制当前仍在等待完全完成的消息数量。 But because of 1. this BoundedCapacity has no effect.但是因为 1. 这个BoundedCapacity没有效果。

In situations like this I would try to remove the requirement that things get processed in order, so that you can process in parallel, and then report sequentially.在这种情况下,我会尝试删除按顺序处理的要求,以便您可以并行处理,然后按顺序报告。

//The transform block can process everything in parallel,
//but by default the inputs and outputs remain ordered
var processStuff = new TransformBlock<int, string>(async i =>
    {
        Console.WriteLine($"Started with {i}");
        await DoSomethingAsync(i);
        return $"Done with {i}";
    }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 5 });

//This action block is your reporting block that uses the results from
//the transform block, and it will be executed in order.
var useStuff = new ActionBlock<string>(result =>
    {
        Console.WriteLine(result);
    });

//when linking make sure to propagate completion.
processStuff.LinkTo(useStuff, new DataflowLinkOptions { PropagateCompletion = true });

for (int i = 0; i < 5; i++)
{
    Console.WriteLine("Posting {0}", i);
    processStuff.Post(i);
}

//mark the top of your pipeline as complete, and that will propagate
//to the end.
processStuff.Complete();
//wait on your last block to finish processing everything.
await useStuff.Completion;

output from this code produced the following as an example. output 从此代码生成以下示例。 Notice that the "started with" statements are not necessarily even in the order of the postings.请注意,“开始于”语句不一定按发布顺序排列。

Posting 0发布 0
Posting 1发布 1
Posting 2发布 2
Posting 3发布 3
Posting 4发布 4
Started with 1从 1 开始
Started with 0从 0 开始
Started with 2从 2 开始
Started with 4从 4 开始
Started with 3从 3 开始
Done with 0完成 0
Done with 1完成 1
Done with 2完成 2
Done with 3完成 3
Done with 4完成 4

I did, in the meantime, find a solution/workaround, by using two blocks, and passing the asynchronous Task from the first block to the next block, where it is waited for synchronously using .Wait() .与此同时,我确实找到了一个解决方案/解决方法,方法是使用两个块,并将异步任务从第一个块传递到下一个块,在那里它使用.Wait()同步等待。

So, like this:所以,像这样:

using System.Reactive.Linq;
using System.Threading.Tasks.Dataflow;

internal static class Program
{
    public static async Task Main(string[] args)
    {
        var transformBlock = new TransformBlock<int, Task<int>>(async i =>
        {
            Console.WriteLine($"Started with {i}");
            await DoSomethingAsync(i);
            return i;
        });
        var actionBlock = new ActionBlock<Task<int>>(task =>
        {
            task.Wait();
            Console.WriteLine($"Done with {task.Result}");
        });

        transformBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true });

        for (int i = 0; i < 5; i++)
        {
            transformBlock.Post(i);
        }

        transformBlock.Complete();
        await actionBlock.Completion;
    }

    private static Task DoSomethingAsync(int i)
    {
        return Task.Delay(1000);
    }
}}
}

This way the first block just considers itself done with a message almost instantly and is able to handle, in order, the next message which calls DoSomethingAsync directly, without waiting for the response of the previous call.这样,第一个块几乎立即认为自己完成了一条消息,并且能够按顺序处理直接调用DoSomethingAsync的下一条消息,而无需等待前一个调用的响应。

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

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