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



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

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(/*...*/);

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

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

如果你是在。net 4.0上,最直接的方法是从每个延续中的前一个任务访问Task.Result,或者,如果它不返回结果,使用Task.Wait(),就像你在示例代码中所做的那样。然而,你很可能最终得到一个嵌套的AggregateException对象树,你需要在以后解开它,以便得到"真正的"异常。(同样,. net 4.5使这更容易。当Task.Result抛出AggregateException时,Task.GetAwaiter().GetResult()——在其他方面是等价的——抛出底层异常。

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

如果你不想在异常事件(即日志记录)中做任何特别的事情,只是希望异常被传播,那么在抛出异常(或在取消事件中)时不要运行延续。

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

如果您显式地想要处理异常的情况(可能要做日志记录,将抛出的异常更改为其他类型的异常(可能带有额外的信息,或者模糊不应该公开的信息)),那么您可以使用OnlyOnFaulted选项添加延续(可能除了正常的情况延续)。

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

方法有两种形式:

  • Then:继续返回Task,自动调用Unwrap()
  • Select:继续返回一个对象,没有调用Unwrap()发生。这个方法只适用于轻量级的延续,因为它总是指定TaskContinuationOptions.ExecuteSynchronously

这些方法有以下好处:

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

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

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

如果你想像传递普通对象一样传递异常,那么就把它当作普通对象来处理。让Task返回异常,并在延续中使用Task.Result属性。

相关内容

最新更新