14

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

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

4 回答 4

4

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

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

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

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

于 2014-03-13T16:11:47.757 回答
1

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

这些方法有两种形式:

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

这些方法具有以下优点:

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

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

于 2014-03-13T16:23:42.567 回答
1

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

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

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

于 2013-03-18T17:47:12.530 回答
0

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

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

于 2014-03-10T22:13:57.233 回答