TPL DataFlow:创建自定义拆分块

时间:2013-09-10 13:55:10

标签: .net task-parallel-library tpl-dataflow

在数据流库的帮助下创建自定义拆分块只需要一些帮助,该库是.Net中TPL的一部分。

我想要实现的只是一个简单的自定义块,它接受输入并将其拆分为多个transformblock。这是过滤数据所必需的,我可以在其中记录否定条目并继续使用好的条目。

根据我的需要,它应足以将输入分成两个不同的输出。类头应该看起来像这样......

public abstract class SplitBlock<TInput, TOutputLeft, TOutputRight>

我的问题是我不知道该怎么做。我所知道的是我需要两个TransformBlocks:

var leftBlock  = new TransformBlock<TInput, TOutputLeft>(...)
var rightblock = new TransformBlock<TInput, TOutputRight>(...)

在我的所有尝试中,我最终都有多个ITargetBlock用于存储左右块的输入,但这不可能,是吗?

我感谢你能给予的每一个暗示。

1 个答案:

答案 0 :(得分:5)

我首先想一想该类的通用接口应该如何。我认为最简单的解决方案是:

public class SplitBlock<TInput, TOutputLeft, TOutputRight>
{
    public ITargetBlock<TInput> Input { get; }
    public ISourceBlock<TOutputLeft> LeftOutput { get; }
    public ISourceBlock<TOutputRight> RightOutput { get; }
}

由此,实现自然地实现:一个输入块连接到两个输出块。唯一的问题是实际处理是否应该在输出块中完成(就像你在两个TransformBlock s中建议的那样)或在输入块中。

如果要在输出块中进行处理,输入块可以是ActionBlock,它将输入发送到两个输出,输出将是TransformBlock,如您所建议的那样。

public class SplitBlock<TInput, TOutputLeft, TOutputRight>
{
    private ActionBlock<TInput> input;
    private TransformBlock<TInput, TOutputLeft> leftOutput;
    private TransformBlock<TInput, TOutputRight> rightOutput;

    public ITargetBlock<TInput> Input { get { return input; } }
    public ISourceBlock<TOutputLeft> LeftOutput { get { return leftOutput; } }
    public ISourceBlock<TOutputRight> RightOutput { get { return rightOutput; } }

    public SplitBlock(
        Func<TInput, TOutputLeft> leftTransform,
        Func<TInput, TOutputRight> rightTransform)
    {
        input = new ActionBlock<TInput>(
            x =>
            {
                leftOutput.Post(x);
                rightOutput.Post(x);
            });
        leftOutput = new TransformBlock<TInput, TOutputLeft>(leftTransform);
        rightOutput = new TransformBlock<TInput, TOutputRight>(rightTransform);

        // TODO handle fault in input correctly
        input.Completion.ContinueWith(
            _ =>
            {
                leftOutput.Complete();
                rightOutput.Complete();
            });
    }
}

(这假设左右变换可以同时处理相同的输入。)

另一方面,如果你想在输入块中执行处理(这对我来说更有意义),那么你可以将ActionBlock作为输入,BufferBlock作为输出,输入块处理输入,然后将结果发送到输出块:

public class SplitBlock<TInput, TOutputLeft, TOutputRight>
{
    private ActionBlock<TInput> input;
    private BufferBlock<TOutputLeft> leftOutput;
    private BufferBlock<TOutputRight> rightOutput;

    public ITargetBlock<TInput> Input { get { return input; } }
    public ISourceBlock<TOutputLeft> LeftOutput { get { return leftOutput; } }
    public ISourceBlock<TOutputRight> RightOutput { get { return rightOutput; } }

    public SplitBlock(
        Func<TInput, Tuple<TOutputLeft, TOutputRight>> combinedTransform)
    {
        input = new ActionBlock<TInput>(
            value =>
            {
                var result = combinedTransform(value);
                leftOutput.Post(result.Item1);
                rightOutput.Post(result.Item2);
            });
        leftOutput = new BufferBlock<TOutputLeft>();
        rightOutput = new BufferBlock<TOutputRight>();

        // TODO handle fault in input correctly
        input.Completion.ContinueWith(
            _ =>
            {
                leftOutput.Complete();
                rightOutput.Complete();
            });
    }

    public SplitBlock(
        Func<TInput, TOutputLeft> leftTransform,
        Func<TInput, TOutputRight> rightTransform)
        : this(x => Tuple.Create(leftTransform(x), rightTransform(x)))
    {}
}