TPL数据流从所有传入节点(多个生产者,1个消费者)创建聚合结果数组

时间:2018-08-29 09:49:15

标签: c# task-parallel-library tpl-dataflow

请注意以下代码示例。我需要一个聚合器节点,该节点可以链接到任意数量的源,等待所有源发送一条消息,然后将它们组合成一个结果[]。

这应该是显而易见的,并且是前进的方向,但是以某种方式我找不到解决方案。 我检查了JoinBlock和TransformaterBlock,但两者似乎都不适合。

using System;
using System.Threading.Tasks.Dataflow;

namespace ConsoleApp2
{
    internal class Program
    {
        private static readonly uint _produceCount = 0;
        private static void Main(string[] args)
        {

            BufferBlock<string> p1 = new BufferBlock<string>();
            BufferBlock<string> p2 = new BufferBlock<string>();

            // a block is required that accepts n sources as input, waits for all inputs to arrive, and then creates a result array from all inputs

            ActionBlock<string[]> c1 = new ActionBlock<string[]>((inputs) =>
            {
                Console.WriteLine(String.Join(',', inputs));
            });

            p1.Post("Produce 1.1");
            p2.Post("Produce 2.1");

            // desired output:
            // "Produce 1.1, Produce 2.1"
            // actually the order is of no importance at this time

        }


    }
}

[编辑] 进一步澄清: 我想有一个方块: -动态等待所有原始注释(在第一条消息到达的时间点)以完成并汇总结果以传递给关注者节点

2 个答案:

答案 0 :(得分:1)

如果您事先知道您的消息来源,我会同时使用JoinBlockTransformBlock。您将必须为每个来源创建一个BufferBlock

首先,JoinBlock等待来自每个源的一条消息并将它们打包在一个元组中。然后TransformBlock从中间元组创建一个结果数组。

如果您事先不知道来源,则需要说明如何期望新块知道何时产生结果。然后应该将该逻辑放入自定义块中,可能采用TransformManyBlock<string,string[]>的形式。

如果要加入动态数量的源,可以创建一个无限的加入块,如下所示:

private static void Main()
{
    var source1 = new BufferBlock<string>();
    var source2 = new BufferBlock<string>();
    var source3 = new BufferBlock<string>();
    var aggregator = CreateAggregatorBlock( 3 );
    var result = new ActionBlock<string[]>( x => Console.WriteLine( string.Join( ", ", x ) ) );
    source1.LinkTo( aggregator );
    source2.LinkTo( aggregator );
    source3.LinkTo( aggregator );
    aggregator.LinkTo( result );

    source1.Post( "message 1" );
    source2.Post( "message 2" );
    source3.Post( "message 3" );

    Console.ReadLine();
}

private static TransformManyBlock<string, string[]> CreateAggregatorBlock( int sources )
{
    var buffer = new List<string>();
    return new TransformManyBlock<string, string[]>( message => {
        buffer.Add( message );
        if( buffer.Count == sources )
        {
            var result = buffer.ToArray();
            buffer.Clear();
            return new[] {result};
        }
        return Enumerable.Empty<string[]>();
    } );
}

这假设您的消息源以相同的速度产生消息。如果不是这种情况,则需要在消息旁边加上消息源的身份,并为每个消息源提供一个缓冲区。

答案 1 :(得分:1)

您可以为此使用非贪婪的BatchBlock。由于不贪心,每个来源将为该批次贡献一个物品。这是originally suggested here。这是一个经过测试的示例: 请注意,作为证明,source1被发送了多个未在批次中显示的项目:

public class DataAggregator
{
    private BatchBlock<string> batchBlock = new BatchBlock<string>(5, new GroupingDataflowBlockOptions() { Greedy = false });
    private ActionBlock<string[]> writer = new ActionBlock<string[]>(strings => strings.ToList().ForEach(str => Console.WriteLine(str)));
    private BufferBlock<string> source1 = new BufferBlock<string>();
    private BufferBlock<string> source2 = new BufferBlock<string>();
    private BufferBlock<string> source3 = new BufferBlock<string>();
    private BufferBlock<string> source4 = new BufferBlock<string>();
    private BufferBlock<string> source5 = new BufferBlock<string>();

    public DataAggregator()
    {
        source1.LinkTo(batchBlock, new DataflowLinkOptions() { PropagateCompletion = true });
        source2.LinkTo(batchBlock, new DataflowLinkOptions() { PropagateCompletion = true });
        source3.LinkTo(batchBlock, new DataflowLinkOptions() { PropagateCompletion = true });
        source4.LinkTo(batchBlock, new DataflowLinkOptions() { PropagateCompletion = true });
        source5.LinkTo(batchBlock, new DataflowLinkOptions() { PropagateCompletion = true });
        batchBlock.LinkTo(writer, new DataflowLinkOptions() { PropagateCompletion = true });
    }

    [Test]
    public async Task TestPipeline()
    {
        source1.Post("string1-1");
        source1.Post("string1-2");
        source1.Post("string1-3");
        source2.Post("string2-1");
        source3.Post("string3-1");
        source4.Post("string4-1");
        source5.Post("string5-1");
        //Should print string1-1 string2-1 string3-1 string4-1 string5-1
        source1.Complete();
        source2.Complete();
        source3.Complete();
        source4.Complete();
        source5.Complete();
        await writer.Completion;
    }
}

输出:

string1-1
string2-1
string3-1
string4-1
string5-1