当在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
中具有三个内部异常-发生一个异常实际上发生的情况,而在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
奇怪的是,此行为的变化取决于您是否等待Task.WhenAll
中的CauseMultipleExceptionsAsync
调用-如果直接返回任务而不是等待它,则所有三个异常都出现在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不包含任何有关它们的信息,您将如何再次找到它们?为什么在CauseMultipleExceptionsAsync
内部等待隐藏这些异常,而直接返回Task.WhenAll
却没有呢?
如果有所作为,则可以在.Net Framework 4.5.2和.Net Core 2.1中复制以上内容。
您正在观察的是await
运算符的行为,而不是await
方法的行为。如果您对Task.WhenAll
为何如此操作感兴趣,则可以从async / await的早期阅读Task.WhenAll
:
可以选择总是抛出第一或总抛出,对于
await
,我们选择总是抛出第一。不过,这并不意味着您无权访问相同的详细信息。在所有情况下,任务的Exception属性仍然返回包含所有异常的this article,因此您可以捕获抛出的任何异常,并在需要时返回以咨询await
。是的,这会导致在AggregateException
和Task.Exception
之间切换时,异常行为之间存在差异,但我们认为这是两个弊端的明显减少。
[如果您希望实现行为类似于task.Wait()
的方法,但是在不失去异步/等待机制的便利的情况下,这很棘手,但是有一些解决方法await task
。