在任务中抛出异常- " wait"vs等()

本文关键字:quot vs wait 任务 抛出异常 | 更新日期: 2023-09-27 18:07:50

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关键字时,不会发生相同的情况。如何解释这种行为?

在任务中抛出异常- " wait"vs等()

目标是使它看起来/操作起来像同步版本。Jon Skeet在他的Eduasync系列中对此做了很好的解释,特别是这篇文章:

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

在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

Stephen Toub详细解释了为什么Task.Wait()和await之间的异常类型不同:

.NET 4.5中的任务异常处理

在设计Task时。等等,在。net 4中,我们选择了总是传播聚合。这一决定受到不覆盖的需要的影响详细信息,还要由任务的主要用例来决定fork/join并行性,其中可能出现多个异常是很常见的。

与Task类似。在高级别等待(即前进进程)(直到任务完成才生成),"等待任务"表示非常不同的主要场景。而不是被用于Fork/join并行,"等待任务"最常见的用法是在取一段顺序的、同步的代码,并将其转换为顺序的、异步的代码段。在代码中执行同步操作,将其替换为由任务表示的异步操作,并"等待"它。因此,当然,你也可以在fork/join操作中使用await。使用Task.WhenAll),它不是80%的情况。更进一步,.NET 4.5看到的介绍System.Runtime.ExceptionServices。ExceptionDispatchInfo,它解决允许跨线程封送异常的问题而不会丢失异常细节,如堆栈跟踪和沃森桶。给定一个异常对象,将其传递给ExceptionDispatchInfo。Create,它返回一个ExceptionDispatchInfo对象,该对象包含对Exception对象的引用和的副本它的细节。抛出异常时,ExceptionDispatchInfo的Throw方法用于恢复内容在不丢失原始信息的情况下抛出异常(当前调用堆栈信息被附加到已经存在的堆栈上(存储在Exception中)。

给定这个,并且仍然可以选择总是抛出第一个或者总是抛出一个聚合,对于await,我们选择总是抛出第一个。但是,这并不意味着你没有访问权限同样的细节。在所有情况下,Task的Exception属性仍然返回一个包含所有异常的AggregateException,因此你可以接住扔出来的,然后回去咨询的任务。在需要时异常。是的,这导致了两者之间的差异在" task.Wait() "answers" await . wait() "之间切换时的异常行为任务,但我们认为这是两害相权取其轻。