【问题标题】:AggregateException from Task.WhenAll only contains first exception when awaited来自 Task.WhenAll 的 AggregateException 仅在等待时包含第一个异常
【发布时间】:2020-08-05 18:46:38
【问题描述】:

当在Task.WhenAll 调用中引发多个异常时,一旦您通过多个等待层等待它,看起来只有一个异常被吸收到任务中。我的印象是Task.Exception.InnerExceptions 属性将包含所有发生的异常,但在某些情况下它们似乎只有一个。

例如,这个示例代码创建了多个抛出异常的任务,然后在它们上等待一个 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);
    }
}

我希望这要么进入catch(AggregateException e) 子句,要么至少在task.Exception.InnerExceptions 中有三个内部异常 - 实际上发生了一个异常,并且只有一个异常在@987654327 中@:

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

更奇怪的是,这种行为会根据您是否在 CauseMultipleExceptionsAsync 中等待 Task.WhenAll 调用而改变 - 如果您直接返回任务而不是等待它,那么所有三个异常都会出现在 task.Exception.InnerException 中。例如,将CauseMultipleExceptionsAsync 替换为:

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

        return Task.WhenAll(tasks);
    }

给出这个结果,所有三个异常都包含在 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

我对此感到很困惑 - 在最初的示例中异常 B 和 C 去了哪里?如果 Task.Exception 不包含有关它们的任何信息,您将如何再次找到它们?为什么 awaiting inside CauseMultipleExceptionsAsync 隐藏了这些异常,而直接返回 Task.WhenAll 却没有?

如果有所作为,我可以在 .Net Framework 4.5.2 和 .Net Core 2.1 中复制上述内容。

【问题讨论】:

  • 谢谢 - 这回答了为什么不输入 catch(AggregateException) 子句,但我不确定它回答了为什么 InnerExceptions 有时包含有时不包含抛出的所有异常的主要问题。

标签: c# .net async-await task-parallel-library aggregateexception


【解决方案1】:

您观察的是await 运算符的行为,而不是Task.WhenAll 方法的行为。如果您对 await 的行为方式感兴趣,可以阅读 async/await 早期的 this article

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

如果您想实现与Task.WhenAll 行为类似的方法, 但在不失去 async/await 机制的便利性的情况下,这很棘手,但有可用的解决方法here

【讨论】:

    猜你喜欢
    • 2013-08-21
    • 2012-08-14
    • 2012-04-11
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 2016-08-22
    • 2019-12-10
    • 2018-08-30
    相关资源
    最近更新 更多