异步方法中的奇怪调试器行为
当我在代码中跨越断点时,我遇到了调试器的奇怪行为:
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关于该主题的系列文章)。