在调用异步代码时始终考虑 AggregateExceptions 中的多个异常是否有意义?

Would it make sense to always account for multiple exceptions in AggregateExceptions when calling into async code?

当调用异步代码时,例如 productUpdate.UpdateAsync(...),它有可能抛出具有多个内部异常或只有一个异常的 AggregateException。这完全取决于内部如何实现 UpdateAsync。

问题: 由于 await 仅解包 AggregateException 中异常列表中的第一个异常,以下特殊代码试图绕过它,但这很笨拙,理想情况下在我调用某些外部库的异步代码的每个地方,那里可能是具有多个异常的 AggregateException。在所有这些地方都有这个有意义吗? (当然可能会进入辅助方法,但这不是这里的重点)然后还有一个问题,即通过捕获这些异常我在做什么有意义的事情。 我认为这在所有地方都没有意义。你的想法?

var t1 = FooAsync();
var t2 = BarAsync();

var allTasks = Task.WhenAll(t1, t2);

try
{
    await allTasks;
}
catch (Exception)
{
    var aggEx = allTasks.Exception as AggregateException;
    // todo: do something with aggEx.InnerExceptions
}

更新: 在此处为用户 Dave 和 运行 的结果添加了完整的重现代码:

using System;
using System.Threading.Tasks;

class Example
{
    static void Main()
    {
        BlahAsync().Wait();
    }

    static async Task BlahAsync()
    {
        var t1 = FooAsync(throwEx: true);
        var t2 = BarAsync(throwEx: true);

        var allTasks = Task.WhenAll(t1, t2);

        try
        {
            await allTasks;
        }
        catch (AggregateException agex)
        {
            Console.WriteLine("Caught an aggregate exception. Inner exception count: " + agex.InnerExceptions.Count);
        }
    }

    static async Task FooAsync(bool throwEx)
    {
        Console.WriteLine("FooAsync: some pre-await code here");

        if (throwEx)
        {
            throw new InvalidOperationException("Error from FooAsync");
        }

        await Task.Delay(1000);

        Console.WriteLine("FooAsync: some post-await code here");
    }

    static async Task BarAsync(bool throwEx)
    {
        Console.WriteLine("BarAsync: some pre-await code here");

        if (throwEx)
        {
            throw new InvalidOperationException("Error from BarAsync");
        }

        await Task.Delay(1000);

        Console.WriteLine("BarAsync: some post-await code here");
    }
}

结果:

FooAsync: some pre-await code here
BarAsync: some pre-await code here

Unhandled Exception: System.AggregateException: One or more errors occurred. (Error from FooAsync) ---> System.InvalidOperationException: Error from FooAsync
   at Example.<FooAsync>d__2.MoveNext() in C:\Users\foo\source\repos\ConsoleApp9\ConsoleApp9\UnderstandingCallStack.cs:line 37
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Example.<BlahAsync>d__1.MoveNext() in C:\Users\foo\source\repos\ConsoleApp9\ConsoleApp9\UnderstandingCallStack.cs:line 20
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at Example.Main() in C:\Users\foo\source\repos\ConsoleApp9\ConsoleApp9\UnderstandingCallStack.cs:line 8

如我评论中所述,您可以像这样显式捕获聚合异常

try
{
    //do you're async code
}
catch (AggregateException ae)
{
    //handle it
}

您可以添加额外的 catch 语句来处理其他异常类型,但请记住始终首先从最具体的异常类型开始,如果您有一个 catch all 异常处理程序(可以说您不应该,但事实并非如此现在)应该始终是最后一个 catch 语句。

现在,当您可以捕获 AggregateException 时,正如您所说,它可以包含许多异常,并且每个异常都可以包含内部异常,因此它有可能成为嵌套异常的完整结构。但是不要害怕! .NET 提供了帮助。您可以对聚合异常调用 .Flatten()。这将为您 return 一个新的 Aggreagete 异常,它有一个内部异常的平面列表,而不是嵌套列表,因此您可以轻松地迭代它或只取最上面的一个,无论您需要做什么。

try
{
    //async code stuff
}
catch(AggregateException ae)
{
    var allExceptions = ae.Flatten().InnerExceptions;
    // now do what you want with the list of exceptions
}
catch (Exception x)
{
    // ooo look here is an example of the second catch statement catching any other exception that isnt an AggregateException)
}

现在,当您捕获聚合异常时,您可以做的另一件很酷的事情是将自定义异常处理程序传递给 AE 实例上的 Handle 方法。如果您想要处理特定类型的异常(例如 FileNotFoundException),但如果有任何其他异常应该作为调用者处理的另一个聚合异常抛出,这将很有用。像这样

try
{
    //async stuff
}
catch (AggregateException ae)
{
    ae.Handle(x => 
        {
            if (x is FileNotFoundException)
            {
                Console.WriteLine("I handled the file not found, don't worry");
                return true;
            }
            return false
        });
}

这里发生的是 Aggregate Exceptions 内部异常中的每个异常都传递给处理程序,如果我们处理它,我们 return true,如果我们不处理它,则为 false。所有未处理的异常(即我们 returned false for)被添加为新聚合异常的内部异常。

这最后一部分可能与你无关,如果你只是想处理聚合异常,无论它包含什么,但它在工具箱 IMO 中是一件好事