我正在使用MDBG示例制作托管.NET调试器。
考虑一些简单的异步示例:
1: private async void OnClick(EventArgs args){
2: var obj = new SomeClass();
3: bool res = await StaticHelper.DoSomeAsyncStuff();
4: if(res){
5: Debug.WriteLine("result is True");
6: }
7: else{
8: Debug.WriteLine("result is False");
9: }
10: someField = obj.Name + "is:" + res.ToString();
11: }
12: public static async Task<bool> DoSomeAsyncStuff(){
13: await Task.Delay(5000);
14: return true;
15: }
使用我的调试器调试此代码我遇到了两个主要问题:
步进行为不可预测: a) 单步执行第3行等等通常应在等待评估完成后转到第4行。但是调试器跳转到第13行并继续从那里踩到。 StepOver behaviour on video
b) 步入第3行,依此类推,只需踩到每一行:第3行 - &gt;第12行 - &gt;第13行(暂停一段时间) - &gt;第14行 - &gt;第15行 - &gt;然而,在第13行进入后,我希望调试器等待评估结果,由于某种原因,步进继续到第3行。之后,调试器等待结果并按预期继续执行。 StepIn behaviour on video
c)如果在等待响应时安排了其他一些工作,调试器会切换到该代码。例如,如果在等待响应期间有一些计时器,则在第13行之后继续对该计时器代码进行评估。相反,像visual studio一样,我希望调试器能够坚持使用它的当前范围,并且在它完全执行之前不要离开它。 Parallel behaviour on video
部分我理解这些问题的根源:编译器创建一个由嵌套结构表示的状态机,其中逻辑封装在MoveNext方法中。这至少解释了为什么步进不能像我期望的情况a)和b)那样工作。当我踩到一些没有符号的代码时(我没有编译器生成的代码符号),我做了一个或多个步骤来获取我的一些代码。这是@Brian Reichle在this related question
中提出的解决方案关于本地变量名称的更改我认为它因Stack Spilling("What's happening" chapter)而发生。但是用ILDASM分析我的程序集我还没有发现任何保存到生成结构的t__stack字段的内容。因此,我猜测为什么变量名不会因异步方法而持久存在。
然而,VisualStudio以某种方式避免了所有这些问题。
那么托管.net调试器应如何处理异步/等待场景中的步进和局部变量解析?
这背后有很多实现代码,但我不确定哪个部分合理显示...