TPL数据流:按顺序处理来自两个传入块的消息

时间:2016-09-20 11:09:36

标签: c# tpl-dataflow

我现在正在使用TPL Dataflow,我需要实现自己的操作块。

此操作块应接受来自两个不同输入块的消息,将这些消息放入单个队列,然后按顺序处理此队列。这里的要点是两个不同的任务不应该同时执行,我不想使用锁。

这是我的解决方案,但它无法正常工作。

public class OrderedActionBlock<TInputLhs, TInputRhs> : IDataflowBlock
    where TInputLhs : class
    where TInputRhs : class
{
    public ITargetBlock<TInputLhs> InputLhs { get { return inputLhs; } }
    public ITargetBlock<TInputRhs> InputRhs { get { return inputRhs; } }


    private readonly BufferBlock<TInputLhs> inputLhs = new BufferBlock<TInputLhs>();
    private readonly BufferBlock<TInputRhs> inputRhs = new BufferBlock<TInputRhs>();

    private ITargetBlock<object> queue;

    public OrderedActionBlock(Action<TInputLhs> actionLhs, Action<TInputRhs> actionRhs)
    {
        queue = new ActionBlock<object>(x =>
        {
            if (x is TInputLhs)
            {
                actionLhs(x as TInputLhs);
            }
            else
            {
                actionRhs(x as TInputRhs);
            }
        });

        inputLhs.LinkTo(queue, new DataflowLinkOptions() { PropagateCompletion = true });
        inputRhs.LinkTo(queue, new DataflowLinkOptions() { PropagateCompletion = true });
    }

    public void Complete()
    {
        queue.Complete();
    }

    public Task Completion
    {
        get { return queue.Completion; }
    }

    public void Fault(Exception exception)
    {
        queue.Fault(exception);
    }
}

简单用法示例:

static void Main(string[] args)
{
    var splitBlock = new SplitBlock<string>(new Predicate<string>(s => s.Length % 2 == 0));

    var batchBlock = new BatchBlock<string>(3);

    var processInOrderBlock = new OrderedActionBlock<string, string[]>(
        new Action<string>((str) =>
        {
            Console.WriteLine(str);
        }),
        new Action<string[]>((batch) =>
        {
            Console.WriteLine("BATCH - " + string.Join(", ", batch));
        }));

    splitBlock.SourceFiltered.LinkTo(processInOrderBlock.InputLhs, new DataflowLinkOptions() { PropagateCompletion = true });
    splitBlock.SourceNonFiltered.LinkTo(batchBlock, new DataflowLinkOptions() { PropagateCompletion = true });
    batchBlock.LinkTo(processInOrderBlock.InputRhs, new DataflowLinkOptions() { PropagateCompletion = true });

    for (int i = 1; i <= 10; i++)
    {
        splitBlock.Post(new string(Enumerable.Repeat('x', i).ToArray()));
    }

    splitBlock.Complete();

    processInOrderBlock.Completion.Wait();

    return;
}

输出:

xx
xxxx
xxxxxx
xxxxxxxx
xxxxxxxxxx
BATCH - x, xxx, xxxxx
Press any key to continue . . .

看起来消息卡在batchBlock中。我不知道为什么。

2 个答案:

答案 0 :(得分:2)

queueinputLhs任何完成时(如果在链接期间使用inputRhs选项),PropagateCompletion = true看似已完整。

所以,我们需要改变这个:

inputLhs.LinkTo(queue, new DataflowLinkOptions() { PropagateCompletion = true });
inputRhs.LinkTo(queue, new DataflowLinkOptions() { PropagateCompletion = true });

到此:

Task.WhenAll(InputLhs.Completion, InputRhs.Completion)
    .ContinueWith(_ => queue.Complete());

答案 1 :(得分:1)

您可能有一个ActionBlock,它接受​​带有两个值的ValueTuple,外加一个索引以指示两个值中的哪个是有效值:

var block = new ActionBlock<(int, Type1, Type2)>(entry =>
{
    var (index, item1, item2) = entry;
    switch (index)
    {
        case 1: DoSomething1(item1); break;
        case 2: DoSomething2(item2); break;
        default: throw new NotImplementedException();
    }
});

block.Post((1, someValue1, default));
block.Post((2, default, someValue2));

这样,通过除去两个中间BufferBlock,可以确保处理的顺序与发布的顺序完全相同。

要使其更美观,更不易出错,您可以创建类似于OrderedActionBlock的类,但要使用“ fake” ITargetBlock<TInputLhs>ITargetBlock<TInputRhs>属性,它们不是真正的块,而仅仅是传播者门面到单个ActionBlock。从一个ITargetBlock到另一个的转换有些棘手,但这是可行的。下面是一个通用的实现。 ActionBlock<TInput1, TInput2>Target1均同时完成时,因此从链接源进行的传播完成应按预期工作。

Target2