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

时间:2017-03-01 18:35:03

标签: 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中运行它 - 结果相同。

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

1 个答案:

答案 0 :(得分:5)

您的代码可以使用Visual Studio 2015在" Debug" 版本中轻松地重现该问题。我只需添加Program.Main(),即可调用DoSomeWork().Wait();,在方法中设置断点并逐步执行。

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

当程序语句出现在await上时,它实际上处于方法中的隐式throw语句中。只是可执行文件的调试数据库没有为该行提供程序声明。

在调试时,有一个提示,即发生了什么。当您单步执行return语句时,您会注意到它直接转到if语句。如果输入的throw语句块确实,则下一个程序语句行实际上将是块的左括号,而不是程序语句。

你也可以添加例如在方法结束时使用if,这将为调试器提供足够的信息以进行同步,而不会显示错误的行号。

有关编译器如何处理Console.WriteLine()方法的其他信息,请参阅Is the new C# async feature implemented strictly in the compiler以及此处提供的链接(包括Jon关于该主题的一系列文章)。