【问题标题】:What is the proper way to propagate exceptions in continuation chains?在延续链中传播异常的正确方法是什么?
【发布时间】:2013-03-06 21:51:46
【问题描述】:

在延续链中传播异常的正确方法是什么?

t.ContinueWith(t2 => 
{
     if(t2.Exception != null)
         throw t2.Exception;

     /* Other async code. */
})
.ContinueWith(/*...*/);   

t.ContinueWith(t2 => 
{
     if(t2.IsFaulted)
         throw t2.Exception;

     /* Other async code. */
})
.ContinueWith(/*...*/);

t.ContinueWith(t2 => 
{
     if(t2.Exception != null)
         return t2;

     /* Other async code. */
})
.ContinueWith(/*...*/);   

t.ContinueWith(t2 => 
{
     if(t2.IsFaulted)
         return t2;

     /* Other async code. */
})
.ContinueWith(/*...*/);


t.ContinueWith(t2 => 
{
     t2.Wait();

     /* Other async code. */
})
.ContinueWith(/*...*/);

t.ContinueWith(t2 => 
{     
     /* Other async code. */
}, TaskContinuationOptions.NotOnFaulted) // Don't think this one works as expected
.ContinueWith(/*...*/);

【问题讨论】:

  • 我假设你的意思不是t.ContinueWith(t =>...),而是t.ContinueWith(t2 =>...),对吧?
  • @Geoff:是的,没错。

标签: c# exception task-parallel-library task continuation


【解决方案1】:

TaskContinuationOptions.OnlyOn... 可能会出现问题,因为如果不满足其条件,它们会导致继续取消。在我理解这一点之前,我编写的代码有一些微妙的问题。

像这样的链式延续实际上很难正确处理。 到目前为止最简单的修复方法是使用新的 .NET 4.5 await 功能。这使您几乎可以忽略您正在编写异步代码的事实。您可以像在同步等效项中一样使用 try/catch 块。对于 .NET 4,可以使用 async targeting pack

如果您使用的是 .NET 4.0,最直接的方法是在每个 continuation 中从前面的任务中访问 Task.Result,或者,如果它不返回结果,请像在示例中一样使用 Task.Wait()代码。但是,您最终可能会得到一个嵌套的 AggregateException 对象树,稍后您需要解开它才能找到“真正的”异常。 (同样,.NET 4.5 使这更容易。虽然 Task.Result 抛出 AggregateExceptionTask.GetAwaiter().GetResult() - 否则等效 - 抛出底层异常。)

重申这实际上不是一个微不足道的问题,您可能会对 Eric Lippert 关于 C# 5 异步代码中的异常处理 herehere 的文章感兴趣。

【讨论】:

  • 非常感谢Task.GetAwaiter().GetResult()
【解决方案2】:

如果您不想在发生异常(即日志记录)时做任何特别的事情,并且只想传播异常,那么在抛出异常时(或在发生异常时)不要运行延续取消)。

task.ContinueWith(t =>
{
    //do stuff
}, TaskContinuationOptions.OnlyOnRanToCompletion);

如果您明确想要处理异常情况(可能进行日志记录,将抛出的异常更改为其他类型的异常(可能带有附加信息,或隐藏不应公开的信息))然后您可以使用 OnlyOnFaulted 选项添加延续(可能是正常情况延续的补充)。

【讨论】:

  • 我注意到这种方法的问题是,如果我返回结果任务,然后继续向链中添加内容,则不会有异常从这一点向前传播......
  • 即链接任何额外的 ContinueWith 不会传播异常......这是不好的。
【解决方案3】:

我们现在在 openstack.net SDK 中使用的方法是 CoreTaskExtensions.cs 中的扩展方法。

方法有两种形式:

  • Then:继续返回一个Task,并自动调用Unwrap()
  • Select:延续返回一个对象,不会调用Unwrap()。此方法仅适用于轻量级延续,因为它始终指定 TaskContinuationOptions.ExecuteSynchronously

这些方法有以下好处:

  1. 避免在前件发生故障或取消时调用延续方法。
  2. 相反,前件的结果变成了链式操作的结果(准确保留异常信息,不将异常包装在多层AggregateException中)。
  3. 允许调用者编写支持错误前件的延续方法,在这种情况下,只有取消的前件任务会绕过延续(为扩展方法指定 supportsErrors=true)。
  4. 返回 Task 的延续将同步执行,并为您调用 Unwrap()

以下比较显示了我们如何将此更改应用于最初广泛使用ContinueWithUnwrapCloudAutoScaleProvider.cs
https://github.com/openstacknetsdk/openstack.net/compare/3ae981e9...299b9f67#diff-3

【讨论】:

    【解决方案4】:

    OnlyOnFaulted 选项适用于以前的Task 无法正确执行其任务的情况。一种方法是在每个延续任务中重新抛出异常,但那将是糟糕的设计。

    如果您想像普通对象一样传递异常,则将其视为一个对象。让Task 返回异常并在继续中使用Task.Result 属性。

    【讨论】:

      猜你喜欢
      • 2018-07-19
      • 1970-01-01
      • 2016-03-01
      • 1970-01-01
      • 2012-02-17
      • 2012-03-29
      • 1970-01-01
      • 1970-01-01
      • 2011-03-20
      相关资源
      最近更新 更多