在ContinueWith中重新发现之前的异常 [英] Rethrowing previous exception inside ContinueWith

查看:97
本文介绍了在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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆