【发布时间】:2020-04-01 20:16:26
【问题描述】:
目标:
我对我在 .Net Core 库中看到的异常行为感到困惑。这个问题的目的是了解为什么它正在做我所看到的。
执行摘要
我认为当调用async 方法时,其中的代码会同步执行,直到它遇到第一个等待。如果是这样的话,如果在那个“同步代码”期间抛出了异常,为什么它没有传播到调用方法呢? (就像普通的同步方法一样。)
示例代码:
在 .Net Core 控制台应用程序中给出以下代码:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
try
{
NonAwaitedMethod();
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}
Console.ReadKey();
}
public static async Task NonAwaitedMethod()
{
Task startupDone = new Task(() => { });
var runTask = DoStuff(() =>
{
startupDone.Start();
});
var didStartup = startupDone.Wait(1000);
if (!didStartup)
{
throw new ApplicationException("Fail One");
}
await runTask;
}
public static async Task DoStuff(Action action)
{
// Simulate starting up blocking
var blocking = 100000;
await Task.Delay(500 + blocking);
action();
// Do the rest of the stuff...
await Task.Delay(3000);
}
}
场景:
按原样运行时,此代码将引发异常,但是,除非您在其上设置断点,否则您不会知道它。 Visual Studio 调试器和控制台都将给出存在问题的任何指示(除了输出屏幕中的一行注释)。
将
NonAwaitedMethod的返回类型从Task交换为void。这将导致 Visual Studio 调试器现在中断异常。它也将在控制台中打印出来。但值得注意的是,在Main中找到的catch语句中NOT 捕获了异常。将
NonAwaitedMethod的返回类型保留为void,但去掉async。还将最后一行从await runTask;更改为runTask.Wait();(这基本上会删除任何异步内容。)运行时,异常会在Main方法的catch语句中捕获。
所以,总结一下:
| Scenario | Caught By Debugger | Caught by Catch |
|------------|--------------------|-----------------|
| async Task | No | No |
| async void | Yes | No |
| void | N/A | Yes |
问题:
我认为因为异常是在await 完成之前引发的,所以它将同步执行直到并通过引发异常。
因此我的问题是:为什么场景 1 或 2 都没有被 catch 语句捕获?
另外,为什么从Task 切换到void 返回类型会导致异常被调试器捕获? (即使我没有使用该返回类型。)
【问题讨论】:
-
@PavelAnikhouski - 我觉得这个问题及其答案更多地是关于在子方法中等待之后发生的事情。我的问题的核心是(据我了解),代码同步执行直到第一次等待。如果调用方法没有在等待,并且如果子方法在遇到任何等待之前发生异常,为什么它不会向上传播(就像正常的同步方法一样)?
-
@Vaccano 你的理解是正确的。并且您的代码确实在调用者方法运行的同一线程上同步执行(在第一次等待之前)。只是当您使用
async时,编译器会使用特殊类型包装您的方法,并为您保留异常,直到您调用await,此时异常会向上传播。 -
附带说明,根据guidelines,异步方法应具有后缀
Async。所以NonAwaitedMethod方法应该命名为NonAwaitedMethodAsync,DoStuff应该命名为DoStuffAsync。这不适用于async void方法。它仅适用于返回可等待类型的异步方法,例如Task。
标签: c# exception .net-core async-await .net-core-3.1