为什么我无法在具有void返回类型的异步函数中捕获异常?
c#
static void Main(string[] args) {
try {
var a = MyMethodAsync();
a.Wait(); // calling Wait throw an AggregateException
}
catch (Exception e) {
Console.WriteLine("Catch");
}
Console.ReadLine();
}
static async Task<String> MyMethodAsync() {
String s = await TestThrowException();
return s;
}
static Task<String> TestThrowException() {
return Task.Run(() => {
throw new DivideByZeroException();
return "placeholder"; // return statement is needed for the compilier to work correctly
});
}
上面的代码有效, Main 方法中的 catch 块可以捕获AggregateException异常(源自TestThrowException并转换为AggregateException)。
但如果我有这样的代码:
static void Main(string[] args) {
try {
MyMethodAsync();
}
catch (Exception e) {
Console.WriteLine("Catch");
}
Console.ReadLine();
}
static async void MyMethodAsync() {
await TestThrowException();
}
static Task<String> TestThrowException() {
return Task.Run(() => {
throw new DivideByZeroException();
return "placeholder";
}
那么 Main 方法中的 catch 块无法捕获任何异常,这是为什么呢?
回答
任何时候async void,您基本上都会破坏正确发出完成和失败信号的能力;它可以报告失败的唯一方法是异常是否立即await发生并且在任何不完整之前发生——即同步发生。在您的情况下,Task.Run保证这不是同步的,因此任何关于结果和失败的知识都丢失了。
从根本上说,永远不要写async void(除非你绝对必须,例如在事件处理程序中)。除了上述,它的问题也已公知一些并发症SynchronizationContext实现(尤其是遗留ASP.NET一个),该装置简单地调用的async void方法是足够的崩溃你的应用程序(至少假设;同步上下文警告应用于更多库作者而不是应用程序作者,因为库作者无法选择应用程序执行环境)。
删除async void. 如果你想返回“nothing”,那么你应该使用async Task或async ValueTask作为签名:
static async Task MyMethodAsync() {
await TestThrowException();
}
(也许也可以简化为)
static Task MyMethodAsync()
=> TestThrowException();
和:
static async Task Main(string[] args) {
try {
await MyMethodAsync();
}
catch (Exception e) {
Console.WriteLine("Catch");
}
Console.ReadLine();
}