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

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

public async Task DoSomeWork() { await Task.Run(() => { Thread.Sleep(1000); }); var test = false; if (test) { throw new Exception("Im in IF body!"); } } 

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

除此之外它即使我await删除行也有效。

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

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

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

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

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

在调试时,有一个提示,就是正在发生的事情。 当您跳过if语句时,您会注意到它直接转到throw语句。 如果确实正在输入if语句块,则下一个程序语句行实际上将是块的左括号,而不是程序语句。

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

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