###简介
在对我的代码进行了一段时间的研究后,我发现异常不一定通过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
行“重置”了异常链,因此看不到除以零的异常,随后在我面前炸毁了 “A Task's exception (s) 没有被观察到……”
所以...我自己写了一些扩展方法(有很多不同的重载,但基本上都是这样做的):
public static Task ContinueWithEx(this Task task, Action<Task> continuation)
{
return task.ContinueWith(t =>
{
if(t.IsFaulted) throw t.Exception;
continuation(t);
});
}
再四处搜索,我发现了 this博客文章,他在其中提出了类似的解决方案,但使用的是 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;
}
###问题
这两个版本是否严格等效?还是 throw t.Exception
和 tcs.TrySetException(t.Exception)
之间存在细微差别?
此外,整个互联网上显然只有一个人这样做的事实是否表明我错过了这样做的惯用方式?
最佳答案
两者之间的区别很微妙。在第一个示例中,您抛出了任务返回的异常。这将触发 CLR 中的正常异常抛出和捕获,ContinueWith
将捕获并包装它并将其传递给链中的下一个任务。
在第二个过程中,您正在调用 TrySetException
,它仍然会包装异常并将其传递给链中的下一个任务,但不会触发任何 try/catch 逻辑。
ContinueWithEx
之后的最终结果是 AggregateException(AggregateException(DivideByZeroException))
。我看到的唯一区别是内部 AggregateException 在第一个示例中设置了堆栈跟踪(因为它被抛出),而在第二个示例中没有堆栈跟踪。
两者都不太可能比另一个快得多,但我个人更喜欢后者以避免不必要的抛出。
我曾做过这样的事情,其中延续返回了一个结果。我将其称为 Select
,处理上一个任务被取消的情况,提供重载以修改异常而不是结果或除结果之外,并使用 ExecuteSynchronously
选项。当延续本身会返回一个任务时,我根据 this article 中的代码调用了 Then
关于c# - 在 ContinueWith 中重新抛出先前的异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11839959/