【问题标题】:Combining dataflow results合并数据流结果
【发布时间】:2017-08-09 04:30:18
【问题描述】:

我正在开发一个 Dataflow 管道,它读取文件集合,并针对每个文件中的每一行执行一系列 Dataflow 块。

在为文件中的每一行完成所有步骤后,我想在文件本身上执行更多块,但我不知道这是怎么可能的。

通过TransformManyBlock 拆分处理很简单,但是如何合并呢?
我已经习惯了 Apache Camel 的 Splitter 和 Aggregator 功能 - 或者 Dataflow 的意图和我想要的用途之间存在根本脱节?

【问题讨论】:

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


【解决方案1】:

您可能应该查看JoinBlock 和BatchedJoinBlock。他们都可以加入两个或三个来源,你可以为他们设置一个过滤器来专门收集一些项目。

一些对你有用的链接:

【讨论】:

  • 我可能误解了范式,但我不确定 JoinBlock 在我的用例中如何工作,因为我有 n 输入等待。例如,每个文件可能有 1,000 行要处理。每行都有一个Blocks 序列,用于处理该行的内容。一旦文件中所有行的所有块都完成,我想继​​续处理文件。从概念上讲,这些是n 每个文件的子流序列。 JoinBlock 看起来适合处理一组固定的输入。
  • 从您的问题来看,这并不明显。这种情况可以通过不同的方式解决。例如,通过过滤块或增加MaxDegreeOfParallelism。没有您的代码,很难为您提供帮助。
  • 您找到解决问题的方法了吗?我有一个非常相似的问题,很想听听你是如何实现它的。在我看来,您必须实现自己的 Block。感谢您的回复
【解决方案2】:

拆分器和聚合器块的正确实现将过于复杂而无法实现,并且使用起来也过于繁琐。所以我想出了一个更简单的 API,它封装了两个块,一个主块和一个细节块。每个块的处理选项都不同。主块执行拆分和聚合操作,而细节块执行每个细节的转换。关于两组单独选项的唯一要求是CancellationToken 必须对两者相同。可以为每个块单独设置所有其他选项(MaxDegreeOfParallelism、BoundedCapacity、EnsureOrdered、TaskScheduler 等)。

public static TransformBlock<TInput, TOutput>
    CreateSplitterAggregatorBlock<TInput, TDetail, TDetailResult, TOutput>(
    Func<TInput, Task<IEnumerable<TDetail>>> split,
    Func<TDetail, Task<TDetailResult>> transformDetail,
    Func<TInput, TDetailResult[], TOutput> aggregate,
    ExecutionDataflowBlockOptions splitAggregateOptions = null,
    ExecutionDataflowBlockOptions transformDetailOptions = null)
{
    if (split == null) throw new ArgumentNullException(nameof(split));
    if (aggregate == null) throw new ArgumentNullException(nameof(aggregate));
    if (transformDetail == null)
        throw new ArgumentNullException(nameof(transformDetail));
    splitAggregateOptions = splitAggregateOptions ??
        new ExecutionDataflowBlockOptions();
    var cancellationToken = splitAggregateOptions.CancellationToken;
    transformDetailOptions = transformDetailOptions ??
        new ExecutionDataflowBlockOptions() { CancellationToken = cancellationToken };
    if (transformDetailOptions.CancellationToken != cancellationToken)
        throw new ArgumentException("Incompatible options", "CancellationToken");

    var detailTransformer = new ActionBlock<Task<Task<TDetailResult>>>(async task =>
    {
        try
        {
            task.RunSynchronously();
            await task.Unwrap().ConfigureAwait(false);
        }
        catch { } // Suppress exceptions (errors are propagated through the task)
    }, transformDetailOptions);

    return new TransformBlock<TInput, TOutput>(async item =>
    {
        IEnumerable<TDetail> details = await split(item); //continue on captured context
        TDetailResult[] detailResults = await Task.Run(async () =>
        {
            var tasks = new List<Task<TDetailResult>>();
            foreach (var detail in details)
            {
                var taskFactory = new Task<Task<TDetailResult>>(
                    () => transformDetail(detail), cancellationToken);
                var accepted = await detailTransformer.SendAsync(taskFactory,
                    cancellationToken).ConfigureAwait(false);
                if (!accepted)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    throw new InvalidOperationException("Unexpected detail rejection.");
                }
                var task = taskFactory.Unwrap();
                // Assume that the detailTransformer will never fail, and so the task
                // will eventually complete. Guarding against this unlikely scenario
                // with Task.WhenAny(task, detailTransformer.Completion) seems overkill.
                tasks.Add(task);
            }
            return await Task.WhenAll(tasks).ConfigureAwait(false);
        }); // continue on captured context
        return aggregate(item, detailResults);
    }, splitAggregateOptions);
}

// Overload with synchronous lambdas
public static TransformBlock<TInput, TOutput>
    CreateSplitterAggregatorBlock<TInput, TDetail, TDetailResult, TOutput>(
    Func<TInput, IEnumerable<TDetail>> split,
    Func<TDetail, TDetailResult> transformDetail,
    Func<TInput, TDetailResult[], TOutput> aggregate,
    ExecutionDataflowBlockOptions splitAggregateOptions = null,
    ExecutionDataflowBlockOptions transformDetailOptions = null)
{
    return CreateSplitterAggregatorBlock(
        item => Task.FromResult(split(item)),
        detail => Task.FromResult(transformDetail(detail)),
        aggregate, splitAggregateOptions, transformDetailOptions);
}

以下是此块的使用示例。输入是包含逗号分隔数字的字符串。将每个字符串拆分,然后将每个数字加倍,最后将每个输入字符串的加倍数字相加。

var processor = CreateSplitterAggregatorBlock<string, int, int, int>(split: str =>
{
    var parts = str.Split(',');
    return parts.Select(part => Int32.Parse(part));
}, transformDetail: number =>
{
    return number * 2;
}, aggregate: (str, numbersArray) =>
{
    var sum = numbersArray.Sum();
    Console.WriteLine($"[{str}] => {sum}");
    return sum;
});

processor.Post("1, 2, 3");
processor.Post("4, 5");
processor.Post("6, 7, 8, 9");
processor.Complete();
processor.LinkTo(DataflowBlock.NullTarget<int>());
processor.Completion.Wait();

输出:

[1, 2, 3] => 12
[4, 5] => 18
[6, 7, 8, 9] => 60

【讨论】:

    猜你喜欢
    • 2021-04-21
    • 1970-01-01
    • 2020-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-28
    • 1970-01-01
    • 2018-09-04
    相关资源
    最近更新 更多