调试System.AggregateException-即使在异步代码中也是如此

顾名思义,AggregateException用于在单个异常中对一个或多个异常进行批处理。在本文中,我将向您展示为什么会发生此异常,以及如何在C代码中调试它。

错误的产生和处理

让我们从强制一个产生新的AggregateException开始。这个异常在.NET的任务库中被大量使用,为什么选择一个包含任务的示例是一件轻而易举的事情:

Task task1 = Task.Factory.StartNew(() => { throw new ArgumentException(); } );
Task task2 = Task.Factory.StartNew(() => { throw new UnauthorizedAccessException(); } );

try
{
    Task.WaitAll(task1, task2);
}
catch (AggregateException ae)
{
}

在上面的例子中,我们执行两个任务,每个任务都抛出一个异常。通过调用WaitAll,我们告诉.NET调用并等待这两个任务的结果。这两个任务的组合结果将是AggregateException。

调试这个错误

从调试器中的上一个示例检查异常时,我们在InnerExceptions属性中看到两个抛出的异常:

正如名称中承诺的那样,AggregateException是其他异常的包装器。在该示例中,ArgumentException和UnauthorizedAccessException都可用作内部异常。在某些情况下,您将为每个异常添加一个catch blog,如下所示:
try
{
    ...
}
catch (ArgumentException ex1)
{
    // Log and re-throw
}
catch (UnauthorizedAccessException ex2)
{
    // Log and swallow
}

这样,您就可以向用户生成个别的错误消息,记录异常,但只能重新抛出其中一条或第三条。AggregateException提供了一个方便的名为Handle的小助手方法,而不是在InnerExceptions属性中的每个异常上循环:

try
{
    ...
}
catch (AggregateException ae)
{
    ae.Handle(inner =>
    {
        // Log inner
        ...
        return inner is UnauthorizedAccessException;
    });
}

在本例中,Handle方法记录每个异常。func需要返回一个bool,指示是否处理了每个异常。在本例中,我们告诉Handle方法处理了UnauthorizedAccessException,但没有处理ArgumentException。这将导致AggregateException被抛出回调用代码,但InnerExceptions属性中没有UnauthorizedAccessException(因为我们将该异常标记为已处理)。

来自HttpClient的AggregateExceptions

在处理System.Net.Http.HttpClient类时,可能会遇到AggregateException。这主要是在使用旧API实现异步代码时(Wait、ContinueWith等)。让我们看一个例子:
try
{
    var client = new HttpClient();
    var task = client.GetStringAsync("https://httpstat.us/500");
    task.Wait();
    var result = task.Result;
}
catch (AggregateException ex)
{
    throw ex;
}

对https://httpstat.us/500的请求返回一个HTTP状态代码500,该代码抛出一个HttpRequestException。我使用GetStringAsync方法作为示例,但是如果使用其他异步消息(如PostAsync),代码看起来会很相似。如前所述,taskapi的异常被包装在AggregateException中,在InnerExceptions属性中包含HTTP异常。

要获得实际的异常,您有一系列选项。我将重复使用记录异常的必要性来说明。让我们从我在上一个示例中向您展示的Handle方法开始:

try
{
    ...
}
catch (AggregateException ex)
{
    ex.Handle(inner =>
    {
        if (inner is HttpRequestException)
        {
            // Log the exception
            
            return true;
        }
        
        return false;
    });
}

我正在检查内部异常是否是HttpRequestException类型,在这种情况下,告诉Handle方法这个异常已经被处理。在任何其他场景中,我告诉Handle重新抛出原始异常(通过返回false)。
另一种方法是使用ContinueWith方法捕获和处理AggregateException:

var client = new HttpClient();
var task = client
    .GetStringAsync("https://httpstat.us/500")
    .ContinueWith(t =>
    {
        try
        {
            return t.Result;
        }
        catch (AggregateException ex)
        {
            ex.Handle(inner =>
            {
                if (inner is HttpRequestException)
                {
                    // Log the exception

                    return true;
                }

                return false;
            });
        }
        catch (Exception ex)
        {
            throw ex;
        }

        return null;
    });
task.Wait();
var result = task.Result;
我基本上只是把try-catch从ContinueWith方法中移了出来。

最后,如果您在.NET4.5上(可能是,如果不是,应该是),则可以使用await关键字:

var client = new HttpClient();
try
{
    var result = await client.GetStringAsync("https://httpstat.us/500");
}
catch (HttpRequestException ex)
{
    // Log the exception
}
catch (Exception ex)
{
    throw ex;
}

注意代码是如何捕获HttpRequestException而不是AggregateException的。这是因为.NET会自动打开AggregateException并引发底层异常。在大多数情况下,这就是你想要的。移植现有异步代码以使用await关键字的另一个好处。

原文地址:https://www.cnblogs.com/yilang/p/12563904.html