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



当在Task.WhenAll调用中导致多个异常时,在等待一层以上之后,似乎只有一个异常被吸收到Task中。我的印象是Task.Exception.InnerExceptions属性将包含所有发生的异常,但在某些情况下,它们似乎只有一个。

例如,这个示例代码创建了多个抛出异常的Task,然后等待一个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中至少有三个内部异常——实际情况是,只引发了一个异常,而task.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

更奇怪的是,这种行为的变化取决于你是否在CauseMultipleExceptionsAsync中等待Task.WhenAll调用——如果你直接返回Task而不是等待它,那么这三个异常都会出现在task.Exception.InnerException中。例如,将CauseMultipleExceptionsAsync替换为:

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

给出此结果,任务中包含所有三个异常。例外。内部例外:

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不包含任何关于它们的信息,您将如何再次找到它们?为什么在CauseMultipleExceptionsAsync内部等待会隐藏这些异常,而直接返回Task.WhenAll则不会?

如果有区别的话,我可以在.Net Framework 4.5.2和.Net Core 2.1中复制上述内容。

您观察到的是await运算符的行为,而不是Task.WhenAll方法的行为。如果您对await为什么会有这种行为感兴趣,可以阅读async/await:早期的这篇文章

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

如果您希望实现与CCD_ 20行为类似的方法,但是,在不失去async/await机制的便利性的情况下,这是很棘手的,但这里有一些变通方法。

相关内容

最新更新