【问题标题】:Duplicate exceptions with BroadcastBlock in TPL DataflowTPL 数据流中的 BroadcastBlock 重复异常
【发布时间】:2014-02-03 17:47:34
【问题描述】:

我正在尝试使用 TPL 数据流来创建管道。到目前为止一切正常,我的管道定义如下(尽管我的问题只是广播公司,submissionSucceeded,submissionFailed):

// Define tasks
var productListingBatchBuffer = new BufferBlock<PostSubmissionState>();
var splitFile = new TransformBlock<PostSubmissionState, PostSubmissionState>(s => SplitFile(s));
var saveFile = new TransformBlock<PostSubmissionState, PostSubmissionState>(s => SaveFile(s));
var postSubmission = new TransformBlock<PostSubmissionState, PostSubmissionState>(s => PostSubmission(s));
var broadcaster = new BroadcastBlock<PostSubmissionState>(state => state);
var submissionSucceeded = new ActionBlock<PostSubmissionState>(s => SubmissionSucceeded(s));
var submissionFailed = new ActionBlock<PostSubmissionState>(s => SubmissionFailed(s));

// Link em up
productListingBatchBuffer.LinkTo(splitFile, new DataflowLinkOptions() { PropagateCompletion = true });
splitFile.LinkTo(saveFile, new DataflowLinkOptions() { PropagateCompletion = true });
saveFile.LinkTo(postSubmission, new DataflowLinkOptions() { PropagateCompletion = true });
postSubmission.LinkTo(broadcaster, new DataflowLinkOptions() { PropagateCompletion = true });
broadcaster.LinkTo(submissionSucceeded, new DataflowLinkOptions() { PropagateCompletion = true }, state => state.PostSucceeded);
broadcaster.LinkTo(submissionFailed, new DataflowLinkOptions() { PropagateCompletion = true }, state => !state.PostSucceeded);

我遇到的问题是异常的传播。因为我的 BroadcastBlock 将其完成(以及任何故障)传播到两个块,所以如果确实发生异常,它会传播到两个块。因此,当我这样做时

Task.WaitAll(submissionSucceeded.Completion, submissionFailed.Completion);

我最终得到一个包含两个异常的聚合异常。现在我能做的最好的就是过滤这些,即:

try
{
    Task.WaitAll(submissionSucceeded.Completion, submissionFailed.Completion);
}
catch (AggregateException ex)
{
    var uniqueExceptions = new AggregateException(ex.Flatten().InnerExceptions.Distinct());
    Console.WriteLine("An exception was thrown.\n{0}", uniqueExceptions.Flatten());
}

但我想知道是否有更好的方法来做到这一点。即如果只发生一个异常,我只希望引发一个异常。我是 Dataflow 的新手,所以只是发现了所有的约定。

【问题讨论】:

  • 应该submissionSucceededsubmissionFailed 实际上是单独的块吗?如果您将它们组合成一个检查 state.PostSucceeded 内部,那将解决问题。
  • 是的,很可能,在这种情况下。我实际上可能会这样做。但是,更一般地说,这将在任何时候流分裂时发生。例如,在下面的链接中 - 如果在广播器中引发异常,您最终会在存储处理器中复制它:taskmatics.com/blog/…

标签: c# exception tpl-dataflow


【解决方案1】:

我编写了一个 TPL 数据流示例 (https://github.com/squideyes/PodFetch),它采用了稍微不同的方法来完成和错误处理。下面是 Line 的 Program.cs 的 171 到 201 的相关代码:

    scraper.LinkTo(fetcher, link => link != null);
    scraper.LinkTo(DataflowBlock.NullTarget<Link>());

    scraper.HandleCompletion(fetcher);

    Status.Info.Log("Fetching APOD's archive list");

    links.ForEach(link => scraper.Post(link));

    scraper.Complete();

    try
    {
        await fetcher.Completion;

        Status.Finished.Log("Fetched: {0:N0}, Skipped: {1:N0}, Errors: {2:N0}, Seconds: {3:N2}",
            fetched, skipped, errored, (DateTime.UtcNow - startedOn).TotalMilliseconds / 1000.0);
    }
    catch (AggregateException errors)
    {
        foreach (var error in errors.InnerExceptions)
            Status.Failure.Log(error.Message);
    }
    catch (TaskCanceledException)
    {
        Status.Cancelled.Log("The process was manually cancelled!");
    }
    catch (Exception error)
    {
        Status.Failure.Log(error.Message);
    }

如您所见,我将几个 TPL 块链接在一起,然后准备好使用 HandleCompletion 扩展方法处理完成:

    public static void HandleCompletion(
        this IDataflowBlock source, params IDataflowBlock[] targets)
    {
        source.Completion.ContinueWith(
            task =>
            {
                foreach (var target in targets)
                {
                    if (task.IsFaulted)
                        target.Fault(task.Exception);
                    else
                        target.Complete();
                }
            });
    }

非常重要的是,当我将对象传递到链中的第一个块时,我调用了 scraper.Complete()。有了这个,HandleCompletion 扩展方法然后处理继续。而且,由于我正在等待 fetcher(链中完成的最后一个块),因此很容易在 try/catch 中捕获任何由此产生的错误。

【讨论】:

  • 我相信您的 HandleCompletion 方法与 PropagateCompletion 在幕后所做的相同。你选择这样做有什么特别的原因吗?我不认为它解决了我的问题,虽然有趣的是,它确实突出了它,因为很清楚如何通过调用多个目标来复制异常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-03
  • 2015-12-22
相关资源
最近更新 更多