在ContinueWith中重新抛出先前的异常

Ben*_*jol 15 c# exception task-parallel-library

介绍

在困惑我的代码一段时间后,我发现异常不一定传播通过ContinueWith:

int zeroOrOne = 1;
Task.Factory.StartNew(() => 3 / zeroOrOne)
    .ContinueWith(t => t.Result * 2)
    .ContinueWith(t => Console.WriteLine(t.Result))
    .ContinueWith(_ => SetBusy(false))
    .LogExceptions();
Run Code Online (Sandbox Code Playgroud)

在这个例子中,该SetBusy行"复位"异常链,所以通过零异常的鸿沟是没有看到,随后在我的脸炸毁"任务的异常(S),未观察到......"

所以......我给自己写了一个小扩展方法(有很多不同的重载,但基本上都是这样做的):

public static Task ContinueWithEx(this Task task, Action<Task> continuation)
{
     return task.ContinueWIth(t =>
     {
         if(t.IsFaulted) throw t.Exception;
         continuation(t);
     });
}
Run Code Online (Sandbox Code Playgroud)

周围多一点搜索,我碰到这个博客帖子,在那里,他提出了一个类似的解决方案,但使用TaskCompletionSource,其中(转述)看起来是这样的:

public static Task ContinueWithEx(this Task task, Action<Task> continuation)
{
     var tcs = new TaskCompletionSource<object>();
     return task.ContinueWith(t =>
     {
         if(t.IsFaulted) tcs.TrySetException(t.Exception);
         continuation(t);
         tcs.TrySetResult(default(object));
     });
     return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)

这两个版本是否完全相同?或者throw t.Exception和之间有微妙的区别tcs.TrySetException(t.Exception)吗?

此外,在整个互联网上显然只有一个人这样做的事实是否表明我错过了这样做的惯用方法?

Gid*_*rth 9

两者之间的区别是微妙的.在第一个示例中,您将抛出从任务返回的异常.这将触发正常异常抛出和捕获CLR,ContinueWith将捕获并包装它并将其传递给链中的下一个任务.

在第二个调用中TrySetException,它仍将包装异常并将其传递给链中的下一个任务,但不会触发任何try/catch逻辑.

一个ContinueWithEx是最终结果AggregateException(AggregateException(DivideByZeroException)).我看到的唯一区别是内部AggregateException在第一个示例中设置了堆栈跟踪(因为它被抛出)并且在第二个示例中没有堆栈跟踪.

两者都不可能明显快于另一个,但我个人更喜欢第二个避免不必要的投掷.

我做了类似的事情,其中​​延续返回结果.我调用它Select,处理上一个任务被取消的情况,提供重载来修改异常而不是结果或者除了结果之外,并使用了该ExecuteSynchronously选项.当continuation本身返回一个Task时,我会Then根据本文中的代码调用它