替代Dataflow BroadcastBlock,保证交付

时间:2014-08-01 14:04:30

标签: c# multithreading task-parallel-library async-await tpl-dataflow

我需要某种类似于BroadcastBlock的对象,但保证交付。所以我使用了this question的答案。但我真的不清楚这里的执行流程。我有一个控制台应用程序。这是我的代码:

static void Main(string[] args)
{
    ExecutionDataflowBlockOptions execopt = new ExecutionDataflowBlockOptions { BoundedCapacity = 5 };
    List<ActionBlock<int>> blocks = new List<ActionBlock<int>>();

    for (int i = 0; i <= 10; i++)
        blocks.Add(new ActionBlock<int>(num => 
        {
            int coef = i;
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ". " + num * coef); 
        }, execopt));

    ActionBlock<int> broadcaster = new ActionBlock<int>(async num => 
    {
        foreach (ActionBlock<int> block in blocks) await block.SendAsync(num);
    }, execopt);

    broadcaster.Completion.ContinueWith(task =>
        {
            foreach (ActionBlock<int> block in blocks) block.Complete();
        });

    Task producer = Produce(broadcaster);
    List<Task> ToWait = new List<Task>();
    foreach (ActionBlock<int> block in blocks) ToWait.Add(block.Completion);
    ToWait.Add(producer);

    Task.WaitAll(ToWait.ToArray());

    Console.ReadLine();
}

static async Task Produce(ActionBlock<int> broadcaster)
{
    for (int i = 0; i <= 15; i++) await broadcaster.SendAsync(i);

    broadcaster.Complete();
}

每个数字必须按顺序处理,所以我不能在广播块中使用MaxDegreeOfParallelism。但是,接收该号码的所有操作块都可以并行运行。

所以这就是问题:

在输出中我可以看到不同的线程ID。我是否理解正确如下:

执行人员在广播公司中点击await block.SendAsync(num);。 如果当前块未准备好接受该号码,则执行退出广播器并挂起在Task.WaitAll。 当块接受该号码时,广播器中的其余foreach语句将在线程池中执行。 直到最后。 foreach的每次迭代都在线程池中执行。但实际上它是按顺序发生的。

我的理解是对还是错? 如何更改此代码以异步方式将数字发送到所有块?

为了确保如果其中一个区块此时尚未准备好接收该号码,我将不会等待它,所有其他已准备好的区块将收到该号码。并且所有块都可以并行运行。并保证交付。

1 个答案:

答案 0 :(得分:5)

假设您希望broadcaster一次处理一个项目,同时启用目标块同时接收该项目,您需要更改broadcaster以同时为所有块提供该数字时间,然后异步等待所有一起接受它,然后再转到下一个数字:

var broadcaster = new ActionBlock<int>(async num => 
{
    var tasks = new List<Task>();
    foreach (var block in blocks)
    {
        tasks.Add(block.SendAsync(num));
    }
    await Task.WhenAll(tasks);
}, execopt);

现在,在这种情况下,在等待之后你没有工作,你可以稍微优化,同时仍然返回一个等待的任务:

ActionBlock<int> broadcaster = new ActionBlock<int>(
    num => Task.WhenAll(blocks.Select(block => block.SendAsync(num))), execopt);