【问题标题】:TPL DataFlow unable to handle an exception in ActionBlockTPL DataFlow 无法处理 ActionBlock 中的异常
【发布时间】:2017-09-07 08:19:03
【问题描述】:

我正在尝试将来自ActionBlock<int> 的一条消息的副本发送给多个同样是ActionBlock<int> 的消费者。这很好用,但是如果目标块之一引发异常,似乎这不会传播到源块。这是我尝试处理异常的方法,但它永远不会进入catch 部分:

static void Main(string[] args)
{
    var t1 = new ActionBlock<int>(async i =>
    {
        await Task.Delay(2000);
        Trace.TraceInformation($"target 1 | Thread {System.Threading.Thread.CurrentThread.ManagedThreadId} | message {i}");
    }, new ExecutionDataflowBlockOptions { BoundedCapacity = 5 });

    var t2 = new ActionBlock<int>(async i =>
    {
        await Task.Delay(1000);
        Trace.TraceInformation($"target 2 | Thread {System.Threading.Thread.CurrentThread.ManagedThreadId} | message {i}");
    }, new ExecutionDataflowBlockOptions { BoundedCapacity = 5 });

    var t3 = new ActionBlock<int>(async i =>
    {
        await Task.Delay(100);
        Trace.TraceInformation($"target 3 | Thread {System.Threading.Thread.CurrentThread.ManagedThreadId} | message {i}");
        if (i > 5)
            throw new Exception("Too big number");
    }, new ExecutionDataflowBlockOptions { BoundedCapacity = 5 });

    var targets = new [] { t1, t2, t3};

    var broadcaster = new ActionBlock<int>(
        async item =>
        {
            var processingTasks = targets.Select(async t =>
            {
                try
                {
                    await t.SendAsync(item);
                }
                catch
                {
                    Trace.TraceInformation("handled in select"); // never goes here
                }
            });

            try
            {
                await Task.WhenAll(processingTasks);
            }
            catch
            {
                Trace.TraceInformation("handled"); // never goes here
            }
        });

    for (var i = 1; i <= 10; i++)
        broadcaster.Post(i);
}

我不确定我在这里遗漏了什么,但我希望能够检索异常以及哪个目标块出现故障。

【问题讨论】:

  • You only await from Task from SendAsync 仅表明该项目是否被目标接受。如果任何一个目标引发异常,该异常将附加到该目标的Completion 任务。为了观察该异常,您需要await 该任务,即await t3.Completion
  • 一个简单的解决方法是用if (!await t.SendAsync(item)) await t.Completion; 替换await t.SendAsync(item);,这会将异常传播到你最内心的try/catch。然后,您可以再次抛出或向新异常添加信息,例如哪个块出现故障。然后你需要处理错误的broadcaster,但你明白了。
  • @JSteward 谢谢!我已经替换为if (!await t.SendAsync(item)) await t.Completion;,现在一切正常。将其发布为答案,以便我接受。

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


【解决方案1】:

如果一个块进入故障状态,它将不再接受新项目,并且它抛出的Exception 将附加到其Completion 任务和/或如果在管道中链接,则随着其完成传播。要观察Exception,如果块拒绝更多项目,您可以await 完成。

var processingTasks = targets.Select(async t =>
{
    try
    {
        if(!await t.SendAsync(item))
            await t.Completion;
    }
    catch
    {
        Trace.TraceInformation("handled in select"); // never goes here
    }
});

【讨论】:

  • 一个问题。在我看来,即使t.SendAsync(item) 按顺序发送消息,t 之后的下一个块也会无序地接收它们。我虽然 DataFlow 保证消息处理的顺序?
  • 您的每个目标都将按照发送到您的broadcaster 的顺序接收消息。如果最多一个目标失败,broadcaster 将出错并拒绝新消息。您的 await Task.WhenAll 带有默认阻止选项可确保所有目标在处理下一个消息之前都已接受单个消息。您能否提供一个显示乱序消息的示例或用例?
  • 这是我的错。我将ActionBlock&lt;int&gt; 的主体作为参数传递为Action&lt;int&gt; 而不是Func&lt;int, Task&gt;
  • 啊,很高兴你明白了。
  • 但是我注意到如果我在两者之间引入另一个块,if(!await t.SendAsync(item)) 将不再起作用。假设t.SendAsync(item),其中t 是一个新块BufferBlock&lt;int&gt;,链接到引发异常的实际ActionBlock&lt;int&gt;。异常不会像以前那样回到源头,因此它永远不会被捕获。处理它的最佳策略是什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-14
  • 2012-04-10
  • 2017-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多