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

Alt*_*sta 3 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!");
     }
}
Run Code Online (Sandbox Code Playgroud)

调试器进入if正文.值得注意的是,异常并没有被抛出,只是看起来就是这样.因此,如果您正确放置断点,则无法重现throw.你必须将它放在上面并向下走到if身体才能抓住它.同样适用于任何类型的异常实例(以及显式null),甚至可以return代替throw.

除此之外它即使我删除行也可以工作await.

我试图从不同的PC运行此代码片段,因此它不是PC的麻烦.另外我认为它是VS代码中的错误,并试图在JetBrains的Rider中运行它 - 结果相同.

我确定这是异步的事情,但它是如何明确地起作用的?

Pet*_*iho 5

您的代码使用Visual Studio 2015 在"Debug"构建中轻松地重现问题.我只需要Program.Main()通过调用来添加,DoSomeWork().Wait();在方法中设置断点并逐步执行它.

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

当程序语句出现时throw,它实际上是在方法中的隐式return语句中.只是可执行文件的调试数据库不为该行提供程序语句.

在调试时,有一个暗示,就是正在发生的事情.当你跳过if声明时,你会发现它直接进入throw声明.如果确实正在输入if语句块,则下一个程序语句行实际上将是块的左括号,而不是程序语句.

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

有关async编译器如何处理方法的其他信息,请参阅编译器中是否严格实现新的C#异步功能,以及此处提供的链接(包括Jon关于该主题的系列文章).