static async void Main(string[] args)
{
Task t = new Task(() => { throw new Exception(); });
try
{
t.Start();
t.Wait();
}
catch (AggregateException e)
{
// When waiting on the task, an AggregateException is thrown.
}
try
{
t.Start();
await t;
}
catch (Exception e)
{
// When awating on the task, the exception itself is thrown.
// in this case a regular Exception.
}
}
在TPL中,当在一个Task中抛出一个异常时,它被一个AggregateException包装 但使用等待关键字时也不会发生同样的情况 这种行为有什么解释?
答案 0 :(得分:9)
目标是使其外观/行为与同步版本相同。 Jon Skeet在他的Eduasync系列中做了很好的解释,特别是这篇文章:
答案 1 :(得分:2)
在TPL中使用AggregateException
因为你可以在等待操作中有多个任务(任务可以附加子任务),所以很多任务都可以抛出异常。查看子任务中的例外部分
https://msdn.microsoft.com/ru-ru/library/dd997417(v=vs.110).aspx
在await
中,您总是只有一项任务
另见https://msdn.microsoft.com/ru-ru/library/dd997415(v=vs.110).aspx
答案 2 :(得分:0)
Stephen Toub很好地解释了为什么Task.Wait()和await之间的异常类型有所不同:
Task Exception Handling in .NET 4.5
在.NET 4中设计Task.Wait时,我们选择始终传播 骨料。该决定受到不覆盖需求的影响 细节,但根据当时任务的主要用例, fork / join并行性的概念,其中可能有多个异常 很常见。
与任务类似,请高层等待(即前进) 直到任务完成才创建),“等待任务”代表 不同的主要方案集。而不是被用于 fork / join并行性,“等待任务”的最常见用法是 采取顺序,同步的代码并将其转换为 顺序的异步代码段。在代码中的位置 您执行同步操作,将其替换为 任务代表的异步操作并“等待”它。因此, 虽然您当然可以使用await进行fork / join操作(例如 利用Task.WhenAll),情况并非80%。此外,.NET 4.5 看到介绍 System.Runtime.ExceptionServices.ExceptionDispatchInfo,它解决了 允许您封送跨线程异常的问题 不会丢失堆栈跟踪和Watson存储桶等异常详细信息。 给定一个异常对象,将其传递给 ExceptionDispatchInfo.Create,它返回一个ExceptionDispatchInfo 对象,该对象包含对Exception对象的引用和的副本 其细节。当需要抛出异常时, ExceptionDispatchInfo的Throw方法用于还原内容 并抛出异常而不会丢失原始信息 (当前的调用堆栈信息会附加到已经 存储在异常中)。
考虑到这一点,又可以选择始终抛出第一个 或总是抛出总计,对于“等待”,我们选择总是抛出 首先。但是,这并不意味着您无权访问 相同的细节。在任何情况下,任务的“例外”属性仍然 返回一个包含所有异常的AggregateException,因此 您可以抓住任何抛出的东西,然后回去咨询 Task.Exception必要时。是的,这导致 在“ task.Wait()”和“ await”之间切换时的异常行为 任务”,但我们认为这是两个弊端的明显减少。