在ContinueWith中重新发现之前的异常 [英] Rethrowing previous exception inside ContinueWith
问题描述
简介
在我的代码困惑了一段时间后,我发现异常不会通过 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(此任务任务,Action< Task>继续)
{
return task.ContinueWIth(t =>
{
if(t.IsFaulted)throw t.Exception;
continuation(t);
});
}
搜索更多,我发现这个博客文章,在那里他提出了一个类似的解决方案,但使用了TaskCompletionSource, )看起来像这样:
public static Task ContinueWithEx(此任务任务,Action< Task>继续)
{
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;
}
问题
这两个版本是否严格等同?或者在 throw t.Exception
和 tcs.TrySetException(t.Exception)
之间有一个微妙的区别? >
另外,在整个互联网上显然只有另外一个人这样做的事实表明我错过了这样做的惯用方式吗?
两者之间的区别是微妙的。在第一个示例中,您将抛出该任务返回的异常。这将触发正常的异常在CLR中抛出和捕获, ContinueWith
将捕获并将其包装并传递给链中的下一个任务。
在第二个中,您调用 TrySetException
,它仍将包装异常并将其传递给链中的下一个任务,但不会触发任何try / catch逻辑。
一个 ContinueWithEx
之后的最终结果是 AggregateException(AggregateException (DivideByZeroException))
。唯一的区别是,内部的AggregateException在第一个例子中设置了一个堆栈跟踪(因为它被抛出),而在第二个例子中没有堆栈跟踪。
可能比其他人快得多,但我个人更喜欢第二个来避免不必要的投掷。
我已经做了类似这样的事情,继续返回结果。我打电话给它选择
,处理上一个任务被取消的情况,提供重载来修改异常,而不是或除了结果,并使用 ExecuteSynchronously
选项。当继续本身返回一个任务时,我根据然后 /archive/2010/11/21/10094564.aspxrel =noreferrer>这篇文章
Intro
After puzzling over my code for a while, I discovered that exceptions don't necessarily propagate through ContinueWith
:
int zeroOrOne = 1;
Task.Factory.StartNew(() => 3 / zeroOrOne)
.ContinueWith(t => t.Result * 2)
.ContinueWith(t => Console.WriteLine(t.Result))
.ContinueWith(_ => SetBusy(false))
.LogExceptions();
In this example, the SetBusy
line 'resets' the chain of exceptions, so the divide by zero exception isn't seen and subsequently blows up in my face with "A Task's exception(s) were not observed..."
So... I wrote myself a little extension method (with tons of different overloads, but basically all doing this):
public static Task ContinueWithEx(this Task task, Action<Task> continuation)
{
return task.ContinueWIth(t =>
{
if(t.IsFaulted) throw t.Exception;
continuation(t);
});
}
Searching around a bit more, I came across this blog post, where he proposes a similar solution, but using a TaskCompletionSource, which (paraphrased) looks like this:
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;
}
Question
Are these two versions strictly equivalent? Or is there a subtle difference between throw t.Exception
and tcs.TrySetException(t.Exception)
?
Also, does the fact that there's apparently only one other person on the whole internet who's done this indicate that I'm missing the idiomatic way of doing this?
The difference between the two is subtle. In the first example, you are throwing the exception returned from the task. This will trigger the normal exception throwing and catching in the CLR, the ContinueWith
will catch and wrap it and pass it to the next task in the chain.
In the second you are calling TrySetException
which will still wrap the exception and pass it to the next task in the chain, but does not trigger any try/catch logic.
The end result after one ContinueWithEx
is AggregateException(AggregateException(DivideByZeroException))
. The only difference I see is that the inner AggregateException has a stack trace set in the first example (because it was thrown) and no stack trace in the second example.
Neither is likely to be significantly faster than the other, but I would personally prefer the second to avoid unneeded throws.
I have done something like this where the continuation returned a result. I called it Select
, handled cases of the previous task being cancelled, provided overloads to modify the exception instead of or in addition to the result, and used the ExecuteSynchronously
option. When the continuation would itself return a Task, I called that Then
instead based on the code from this article
这篇关于在ContinueWith中重新发现之前的异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!