C# 在ContinueWith中重新引发上一个异常 简介

C# 在ContinueWith中重新引发上一个异常 简介,c#,exception,task-parallel-library,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)) .

在对我的代码困惑了一段时间之后,我发现异常不一定会通过
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
行“重置”了异常链,因此看不到被零除的异常,并随后在我面前爆发“未观察到任务的异常…”

所以。。。我为自己编写了一个小小的扩展方法(有大量不同的重载,但基本上都是这样做的):

公共静态任务延续X(此任务,动作延续)
{
返回任务。继续(t=>
{
如果(t.IsFaulted)抛出t.Exception;
续(t);
});
}
再四处搜索一下,我发现了一篇博文,他提出了一个类似的解决方案,但使用了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;
}
公共静态任务延续X(此任务,动作延续)
{
var tcs=new TaskCompletionSource();
返回任务。继续(t=>
{
如果(t.IsFaulted)tcs.TrySetException(t.Exception);
续(t);
TrySetResult(默认值(对象));
});
返回tcs.Task;
}
问题: 这两个版本严格等同吗?或者
throw t.Exception
tcs.TrySetException(t.Exception)
之间有细微的区别吗


另外,整个互联网上显然只有一个人这样做,这是否表明我缺少惯用的方式来做这件事?

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

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

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

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


我做过这样的事情,继续返回一个结果。我将其命名为
Select
,处理上一个任务被取消的情况,提供重载来修改异常而不是结果或结果之外的异常,并使用
ExecuteSynchronously
选项。当continuation本身返回一个任务时,我根据

中的代码调用了
然后
,为什么一开始就有这样一个链?为什么不能将所有操作作为一个
任务执行?@svick,嗯,好问题。因为我甚至没有想过那样做除此之外,在我的实际代码中,最后一个元素(
SetBusy
)需要在不同的调度器上运行。不管怎样,我还是要试试看。@svick,我的示例代码中不清楚的是,每个
ContinueWith
都包含一个任务。如果我尝试在父任务中单独排列这些任务,它会起作用,但有点难看,因为在继续下一个任务之前,我必须显式地
等待
,否则我必须退回到继续,并将问题嵌套。我想是的。谢谢你花时间给我详细的解释。受我问题中链接的文章的启发,我也改成了
,然后是
。但后来我注意到,
then
已经存在并返回了一个“计划”,所以我觉得这可能有点太混乱了。
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;
}