17

###介绍

在对我的代码困惑了一段时间后,我发现异常不一定会通过以下方式传播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行“重置”异常链,因此看不到除以零异常,随后在我的脸上炸开了“未观察到任务的异常......”

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

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

再四处搜索,我发现了这篇博文,他提出了一个类似的解决方案,但使用了 TaskCompletionSource,它(意译)看起来像这样:

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

###Question 这两个版本是否严格等效?throw t.Exception还是和之间有细微的差别tcs.TrySetException(t.Exception)

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

4

1 回答 1

12

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

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

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

两者都不太可能比另一个快得多,但我个人更喜欢第二个以避免不必要的投掷。

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

于 2012-08-11T03:41:24.487 回答