在ContinueWith中重新抛出先前的exception

介绍

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

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

在这个例子中, SetBusy行“重置”了exception链,因此看不到除零exception,随后在“我没有观察到任务exception……”的情况下爆炸

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

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

搜索了一下,我发现了这篇博客文章,他在那里提出了类似的解决方案,但是使用了TaskCompletionSource,(转述)如下所示:

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

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

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

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

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

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

两者都不可能明显快于另一个,但我个人更喜欢第二个,以避免不必要的抛出。

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