当我越过代码中的断点时,我遇到了调试器的奇怪行为:
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 中运行它 - 结果相同。
我确定这是异步的,但它是如何明确工作的?
最佳答案
您的代码使用 Visual Studio 2015 在 “调试” 构建中轻松重现了该问题。我只需要添加 Program.Main()
,使用调用 DoSomeWork().Wait();
,在方法中设置断点并单步执行。
至于为什么会这样,这无疑是重写async
方法和生成调试数据库(.pdb)的结合。与迭代器方法类似,向方法添加 async
会导致编译器将您的方法更改为状态机。生成的实际 IL 看起来与原始方法只有一点点相似。也就是说,如果您查看它,您可以识别原始代码的关键组件,但它现在位于一个大的 switch
语句中,该语句处理方法在每个 await< 返回时发生的事情
语句,然后在每个等待的表达式完成时重新输入。
当程序语句看似在throw
上时,实际上是在方法中隐式的return
语句上。只是可执行文件的调试数据库没有为该行提供程序语句。
有一个提示,在调试时,这就是正在发生的事情。当您越过 if
语句时,您会注意到它直接进入了 throw
语句。如果 if
语句 block 真的被输入,下一个程序语句行实际上是该 block 的左大括号,而不是程序语句。
您还可以添加例如方法末尾的 Console.WriteLine()
,这将为调试器提供足够的信息来进行同步,而不会显示错误的行号。
有关编译器如何处理 async
方法的更多信息,请参阅 Is the new C# async feature implemented strictly in the compiler ,以及那里提供的链接(包括乔恩关于该主题的系列文章)。
关于c# - 异步方法中的奇怪调试器行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42539388/