来自 Task.WhenAll 的 AggregateException 仅包含等待时的第一个异常
AggregateException from Task.WhenAll only contains first exception when awaited
当在 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
更奇怪的是,这种行为会根据您是否等待 CauseMultipleExceptionsAsync
中的 Task.WhenAll
调用而改变 - 如果您直接 return 任务而不是等待它,那么所有三个异常出现在 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
中等待会隐藏这些异常,而 return 直接 Task.WhenAll
却不会?
如果有所不同,我可以在 .Net Framework 4.5.2 和 .Net Core 2.1 中复制上述内容。
您正在观察的是 await
operator, not the behavior of the Task.WhenAll
method. If you are interested in why the await
behaves this way, you could read this article 从 async/await 早期开始的行为:
Having the choice of always throwing the first or always throwing an aggregate, for await
we opt to always throw the first. This doesn’t mean, though, that you don’t have access to the same details. In all cases, the Task’s Exception property still returns an AggregateException
that contains all of the exceptions, so you can catch whichever is thrown and go back to consult Task.Exception
when needed. Yes, this leads to a discrepancy between exception behavior when switching between task.Wait()
and await task
, but we’ve viewed that as the significant lesser of two evils.
如果您想实现与 Task.WhenAll
行为相似的方法,
但在不失去 async/await 机器的便利性的情况下,它很棘手,但有可用的解决方法 .
当在 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
更奇怪的是,这种行为会根据您是否等待 CauseMultipleExceptionsAsync
中的 Task.WhenAll
调用而改变 - 如果您直接 return 任务而不是等待它,那么所有三个异常出现在 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
中等待会隐藏这些异常,而 return 直接 Task.WhenAll
却不会?
如果有所不同,我可以在 .Net Framework 4.5.2 和 .Net Core 2.1 中复制上述内容。
您正在观察的是 await
operator, not the behavior of the Task.WhenAll
method. If you are interested in why the await
behaves this way, you could read this article 从 async/await 早期开始的行为:
Having the choice of always throwing the first or always throwing an aggregate, for
await
we opt to always throw the first. This doesn’t mean, though, that you don’t have access to the same details. In all cases, the Task’s Exception property still returns anAggregateException
that contains all of the exceptions, so you can catch whichever is thrown and go back to consultTask.Exception
when needed. Yes, this leads to a discrepancy between exception behavior when switching betweentask.Wait()
andawait task
, but we’ve viewed that as the significant lesser of two evils.
如果您想实现与 Task.WhenAll
行为相似的方法,
但在不失去 async/await 机器的便利性的情况下,它很棘手,但有可用的解决方法