当我在代码中跨越断点时,我遇到了调试器的奇怪行为:
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中运行它 - 结果相同。
我确定这是异步的,但它是如何明确起作用的?
答案 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关于该主题的一系列文章)。