22
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 包装。但是在使用await关键字
时不会发生同样的情况。 这种行为的解释是什么?

4

3 回答 3

10

目标是让它看起来/表现得像同步版本。Jon Skeet 在他的 Eduasync 系列中很好地解释了这一点,特别是这篇文章:

http://codeblog.jonskeet.uk/2011/06/22/eduasync-part-11-more-sophisticated-but-lossy-exception-handling/

于 2011-09-08T09:41:40.830 回答
3

在 TPLAggregateException中使用是因为您可以在等待操作中拥有多个任务(任务可以附加子任务),因此其中很多都可以抛出异常。在此处查看子任务部分中的异常:

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

于 2017-03-23T10:28:00.100 回答
0

这是 Stephen Toub 的一个很好的详细解释,为什么 Task.Wait() 和 await 之间的异常类型存在差异:

.NET 4.5 中的任务异常处理

在 .NET 4 中设计 Task.Wait 时,我们选择始终传播聚合。该决定受到不覆盖细节的需要的影响,还受到当时任务的主要用例的影响,即 fork/join 并行性,其中多个异常的可能性非常普遍。

虽然在高层次上类似于 Task.Wait(即在任务完成之前不会进行前进),但“等待任务”代表了一组非常不同的主要场景。“等待任务”最常见的用法不是用于 fork/join 并行性,而是将一段顺序的、同步的代码转换为一段顺序的、异步的代码。在代码中执行同步操作的地方,您将其替换为由任务表示的异步操作并“等待”它。因此,虽然您当然可以将 await 用于 fork/join 操作(例如,使用 Task.WhenAll),但这不是 80% 的情况。此外,.NET 4.5 看到 System.Runtime.ExceptionServices.ExceptionDispatchInfo 的引入,它解决了允许您跨线程编组异常而不会丢失堆栈跟踪和 Watson 存储桶等异常详细信息的问题。给定一个异常对象,将其传递给 ExceptionDispatchInfo.Create,后者返回一个 ExceptionDispatchInfo 对象,其中包含对 Exception 对象的引用及其详细信息的副本。当需要抛出异常时,ExceptionDispatchInfo 的 Throw 方法用于恢复异常的内容并在不丢失原始信息的情况下将其抛出(当前调用堆栈信息附加到已存储在 Exception 中的内容中)。它返回一个 ExceptionDispatchInfo 对象,该对象包含对 Exception 对象的引用及其详细信息的副本。当需要抛出异常时,ExceptionDispatchInfo 的 Throw 方法用于恢复异常的内容并在不丢失原始信息的情况下将其抛出(当前调用堆栈信息附加到已存储在 Exception 中的内容中)。它返回一个 ExceptionDispatchInfo 对象,该对象包含对 Exception 对象的引用及其详细信息的副本。当需要抛出异常时,ExceptionDispatchInfo 的 Throw 方法用于恢复异常的内容并在不丢失原始信息的情况下将其抛出(当前调用堆栈信息附加到已存储在 Exception 中的内容中)。

鉴于此,再次选择总是抛出第一个或总是抛出一个聚合,对于“等待”,我们选择总是抛出第一个。但这并不意味着您无法访问相同的详细信息。在所有情况下,Task 的 Exception 属性仍会返回包含所有异常的 AggregateException,因此您可以捕获抛出的任何异常,并在需要时返回查看 Task.Exception。是的,这会导致在“task.Wait()”和“等待任务”之间切换时的异常行为之间存在差异,但我们认为这是两害相权取其轻。

于 2019-07-31T15:10:02.887 回答