c# - 异步方法中的奇怪调试器行为

标签 c# debugging asynchronous return throw

当我越过代码中的断点时,我遇到了调试器的奇怪行为:

public async Task DoSomeWork()
{
     await Task.Run(() => { Thread.Sleep(1000); });

     var test = false;
     if (test)
     {
          throw new Exception("Im in IF body!");
     }
}

调试器进入 if 主体。值得注意的是,异常并没有真正被抛出,而只是看起来像被抛出。因此,如果您将断点放在 throw 上,则无法重现。您必须将它放在上面,然后下到 if 主体才能捕获它。这同样适用于任何类型的异常实例(以及显式 null),甚至适用于 return 而不是 throw

除此之外,即使我删除带有 await 的行,它也能正常工作。

我尝试从不同的 PC 运行此代码片段,因此它不是 PC 的问题。我还认为这是 VS 代码中的错误,并尝试在 JetBrains 的 Rider 中运行它 - 结果相同。

我确定这是异步的,但它是如何明确工作的?

最佳答案

您的代码使用 Visual Studio 2015 在 “调试” 构建中轻松重现了该问题。我只需要添加 Program.Main(),使用调用 DoSomeWork().Wait();,在方法中设置断点并单步执行。

至于为什么会这样,这无疑是重写async方法和生成调试数据库(.pdb)的结合。与迭代器方法类似,向方法添加 async 会导致编译器将您的方法更改为状态机。生成的实际 IL 看起来与原始方法只有一点点相似。也就是说,如果您查看它,您可以识别原始代码的关键组件,但它现在位于一个大的 switch 语句中,该语句处理方法在每个 await< 返回时发生的事情 语句,然后在每个等待的表达式完成时重新输入。

当程序语句看似在throw 上时,实际上是在方法中隐式的return 语句上。只是可执行文件的调试数据库没有为该行提供程序语句。

有一个提示,在调试时,这就是正在发生的事情。当您越过 if 语句时,您会注意到它直接进入了 throw 语句。如果 if 语句 block 真的被输入,下一个程序语句行实际上是该 block 的左大括号,而不是程序语句。

您还可以添加例如方法末尾的 Console.WriteLine(),这将为调试器提供足够的信息来进行同步,而不会显示错误的行号。

有关编译器如何处理 async 方法的更多信息,请参阅 Is the new C# async feature implemented strictly in the compiler ,以及那里提供的链接(包括乔恩关于该主题的系列文章)。

关于c# - 异步方法中的奇怪调试器行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42539388/

相关文章:

c# - 将两个 List<T> 作为一个排序?

c# - 在 c# winforms 中的控件内水平和垂直对齐动态添加的控件

java - 如何删除我没有设置的禁用断点?

c++ - 在 VS 调用堆栈窗口中显示 DLL 的完整路径

javascript - Foreach 在 foreach 中,结果为空,可能是因为异步调用

c# - Inproc 和 Outproc session 状态模式

c# - UWP toast 没有振动?

php - 如何在 PHP 中获取有用的错误消息?

javascript - 如何使用一系列异步请求的结果填充数组

javascript - 我正在阅读关于 promise 的 MDN 文档并想出了一个我无法理解的例子。任何人都可以向我解释流程