异步方法中的奇怪调试器行为
Strange debugger behaviour in async method
当我越过代码中的断点时,我遇到了调试器的奇怪行为:
public async Task DoSomeWork()
{
await Task.Run(() => { Thread.Sleep(1000); });
var test = false;
if (test)
{
throw new Exception("Im in IF body!");
}
}
调试器进入 if
body。值得注意的是,异常并没有真正被抛出,而只是看起来像被抛出。因此,如果将断点放在 throw
上,则无法重现。你必须把它放在上面然后下到 if
body 才能抓住它。这同样适用于任何类型的异常实例(以及显式 null
),甚至适用于 return
而不是 throw
.
此外,即使我删除带有 await
的行,它也能正常工作。
我尝试 运行 来自不同 PC 的此代码片段,因此它不是 PC 问题。我还认为这是 VS 代码中的错误,并尝试在 JetBrains 的 Rider 中 运行 它 - 结果相同。
我确定这是异步的,但它是如何明确工作的?
您的代码使用 Visual Studio 2015 在 "Debug" 构建中轻松重现了该问题。我只需要添加 Program.Main()
,通过调用 DoSomeWork().Wait();
,在方法中设置断点并单步执行它。
至于为什么会这样,这无疑是由于重写了async
方法和生成调试数据库(.pdb)的结合。与迭代器方法类似,向方法中添加 async
会导致编译器将您的方法更改为状态机。生成的实际 IL 看起来与原始方法只有一点点相似。也就是说,如果您查看它,您可以识别原始代码的关键组件,但它现在位于一个大的 switch
语句中,该语句处理每个 [=15= 处的方法 returns 发生的事情] 语句,然后在每个等待的表达式完成时重新输入。
当程序语句出现在throw
上时,实际上是在方法中隐含的return
语句上。只是可执行文件的调试数据库没有为该行提供程序语句。
有一个提示,在调试时,这就是正在发生的事情。当您跨过 if
语句时,您会注意到它直接转到 throw
语句。如果 if
语句块 确实 被输入,下一个程序语句行实际上是该块的左大括号,而不是程序语句。
您还可以添加例如a Console.WriteLine()
在方法的末尾,这将为调试器提供足够的信息来同步,而不是在错误的行号处显示。
有关编译器如何处理 async
方法的更多信息,请参阅 Is the new C# async feature implemented strictly in the compiler 以及那里提供的链接(包括 Jon 关于该主题的系列文章)。
当我越过代码中的断点时,我遇到了调试器的奇怪行为:
public async Task DoSomeWork()
{
await Task.Run(() => { Thread.Sleep(1000); });
var test = false;
if (test)
{
throw new Exception("Im in IF body!");
}
}
调试器进入 if
body。值得注意的是,异常并没有真正被抛出,而只是看起来像被抛出。因此,如果将断点放在 throw
上,则无法重现。你必须把它放在上面然后下到 if
body 才能抓住它。这同样适用于任何类型的异常实例(以及显式 null
),甚至适用于 return
而不是 throw
.
此外,即使我删除带有 await
的行,它也能正常工作。
我尝试 运行 来自不同 PC 的此代码片段,因此它不是 PC 问题。我还认为这是 VS 代码中的错误,并尝试在 JetBrains 的 Rider 中 运行 它 - 结果相同。
我确定这是异步的,但它是如何明确工作的?
您的代码使用 Visual Studio 2015 在 "Debug" 构建中轻松重现了该问题。我只需要添加 Program.Main()
,通过调用 DoSomeWork().Wait();
,在方法中设置断点并单步执行它。
至于为什么会这样,这无疑是由于重写了async
方法和生成调试数据库(.pdb)的结合。与迭代器方法类似,向方法中添加 async
会导致编译器将您的方法更改为状态机。生成的实际 IL 看起来与原始方法只有一点点相似。也就是说,如果您查看它,您可以识别原始代码的关键组件,但它现在位于一个大的 switch
语句中,该语句处理每个 [=15= 处的方法 returns 发生的事情] 语句,然后在每个等待的表达式完成时重新输入。
当程序语句出现在throw
上时,实际上是在方法中隐含的return
语句上。只是可执行文件的调试数据库没有为该行提供程序语句。
有一个提示,在调试时,这就是正在发生的事情。当您跨过 if
语句时,您会注意到它直接转到 throw
语句。如果 if
语句块 确实 被输入,下一个程序语句行实际上是该块的左大括号,而不是程序语句。
您还可以添加例如a Console.WriteLine()
在方法的末尾,这将为调试器提供足够的信息来同步,而不是在错误的行号处显示。
有关编译器如何处理 async
方法的更多信息,请参阅 Is the new C# async feature implemented strictly in the compiler 以及那里提供的链接(包括 Jon 关于该主题的系列文章)。