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;
}