简体   繁体   English

来自 Task.WhenAll 的 AggregateException 仅在等待时包含第一个异常

[英]AggregateException from Task.WhenAll only contains first exception when awaited

When causing multiple exceptions in a Task.WhenAll call, it looks like only one of the exceptions is absorbed into the Task once you await it through more than one layer of awaiting.Task.WhenAll调用中导致多个异常时,一旦您通过多个等待层等待它,看起来只有一个异常被吸收到 Task 中。 I was under the impression that the Task.Exception.InnerExceptions property would contain all exceptions that occurred, but under certain circumstances they seem to only have one.我的印象是Task.Exception.InnerExceptions属性将包含所有发生的异常,但在某些情况下它们似乎只有一个。

For example, this sample code creates multiple exception-throwing Tasks and then awaits a Task.WhenAll on them, and then writes to console the exceptions that it is able to catch:例如,此示例代码创建多个引发异常的任务,然后在它们上等待一个 Task.WhenAll,然后写入控制台它能够捕获的异常:

class Program
{
    static async Task Main(string[] args)
    {
        var task = CauseMultipleExceptionsAsync();

        // Delaying until all the Exceptions have been thrown, ensuring it isn't just a weird race condition happening behind the scenes
        await Task.Delay(5000);

        try
        {
            await task;
        }
        catch(AggregateException e)
        {
            // This does not get hit
            Console.WriteLine($"AggregateException caught: Found {e.InnerExceptions.Count} inner exception(s)");
        }
        catch(Exception e)
        {
            Console.WriteLine($"Caught other Exception {e.Message}");

            Console.WriteLine($"task.Exception.InnerExceptions contains {task.Exception.InnerExceptions.Count} exception(s)");
            foreach (var exception in task.Exception.InnerExceptions)
            {
                Console.WriteLine($"Inner exception {exception.GetType()}, message: {exception.Message}");
            }
        }
    }

    static async Task CauseMultipleExceptionsAsync()
    {
        var tasks = new List<Task>()
        {
            CauseExceptionAsync("A"),
            CauseExceptionAsync("B"),
            CauseExceptionAsync("C"),
        };

        await Task.WhenAll(tasks);
    }

    static async Task CauseExceptionAsync(string message)
    {
        await Task.Delay(1000);
        Console.WriteLine($"Throwing exception {message}");
        throw new Exception(message);
    }
}

I was expecting this to either enter the catch(AggregateException e) clause, or at least to have three inner exceptions in task.Exception.InnerExceptions - what actually happens that one one exception is raised, and only only one of the exceptions is in task.Exception.InnerExceptions :我期望这要么进入catch(AggregateException e)子句,要么至少在task.Exception.InnerExceptions中有三个内部异常 - 实际上会发生一个异常,并且只有一个异常在task.Exception.InnerExceptionstask.Exception.InnerExceptions

Throwing exception B
Throwing exception A
Throwing exception C
Caught other Exception A
task.Exception.InnerExceptions contains 1 exception(s)
Inner exception System.Exception, message: A

What is weirder is that this behaviour changes depending on whether you await the Task.WhenAll call in CauseMultipleExceptionsAsync - if you return the Task directly rather than awaiting it, then all three exceptions appear in task.Exception.InnerException .更奇怪的是,这种行为会根据您是否在CauseMultipleExceptionsAsync中等待Task.WhenAll调用而改变 - 如果您直接返回 Task 而不是等待它,那么所有三个异常都会出现在task.Exception.InnerException中。 For instance, replacing CauseMultipleExceptionsAsync with this:例如,将CauseMultipleExceptionsAsync替换为:

    static Task CauseMultipleExceptionsAsync()
    {
        var tasks = new List<Task>()
        {
            CauseExceptionAsync("A"),
            CauseExceptionAsync("B"),
            CauseExceptionAsync("C"),
        };

        return Task.WhenAll(tasks);
    }

Gives this result, with all three exceptions contained in task.Exception.InnerExceptions:给出这个结果,task.Exception.InnerExceptions 中包含所有三个异常:

Throwing exception C
Throwing exception A
Throwing exception B
Caught other Exception A
task.Exception.InnerExceptions contains 3 exception(s)
Inner exception System.Exception, message: A
Inner exception System.Exception, message: B
Inner exception System.Exception, message: C

I'm quite confused about this - where did exceptions B and C go in the initial example?我对此感到很困惑 - 在最初的示例中,异常 B 和 C go 在哪里? How would you go about finding them again if Task.Exception doesn't contain any information about them?如果 Task.Exception 不包含有关它们的任何信息,您将如何再次找到它们? Why does awaiting inside CauseMultipleExceptionsAsync hide these exceptions, while returning the Task.WhenAll directly does not?为什么在CauseMultipleExceptionsAsync中等待会隐藏这些异常,而直接返回Task.WhenAll则不会?

If it makes a difference, I am able to replicate the above in both.Net Framework 4.5.2 and.Net Core 2.1.如果它有所作为,我可以在 .Net Framework 4.5.2 和 .Net Core 2.1 中复制上述内容。

What you are observing is the behavior of the await operator, not the behavior of the Task.WhenAll method.您正在观察的是await运算符的行为,而不是Task.WhenAll方法的行为。 If you are interested in why the await behaves this way, you could read this article from the early days of async/await:如果你对await为什么会这样表现感兴趣,你可以阅读 async/await 早期的这篇文章

Having the choice of always throwing the first or always throwing an aggregate, for await we opt to always throw the first.可以选择总是抛出第一个或总是抛出一个聚合,对于await我们选择总是抛出第一个。 This doesn't mean, though, that you don't have access to the same details.但这并不意味着您无法访问相同的详细信息。 In all cases, the Task's Exception property still returns an AggregateException that contains all of the exceptions, so you can catch whichever is thrown and go back to consult Task.Exception when needed.在所有情况下,Task 的 Exception 属性仍会返回包含所有异常的AggregateException ,因此您可以捕获抛出的任何异常,并在需要时返回 go 以查阅Task.Exception Yes, this leads to a discrepancy between exception behavior when switching between task.Wait() and await task , but we've viewed that as the significant lesser of two evils.是的,这会导致在task.Wait()await task之间切换时的异常行为之间存在差异,但我们认为这是两害相权取其轻。

In case you would like to implement a method similar in behavior to Task.WhenAll , but without losing the convenience of the async/await machinery, it is tricky but there are workarounds available here .如果您想实现与Task.WhenAll行为类似的方法,但又不会失去 async/await 机制的便利性,这很棘手,但这里有可用的解决方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM