【问题标题】:.NET/C# debugging: How to debug a classical heisenbug?.NET/C# 调试:如何调试经典的 heisenbug?
【发布时间】:2011-08-08 07:51:20
【问题描述】:

最近,我遇到了一个经典的Heisenbug。情况如下:

  • 我在一个面板中有一个树列表,即主视图,在右侧的另一个面板中有一个详细视图,它显示有关当前选定树节点的信息。 (非常类似于 Windows 资源管理器。)
  • 当我向树中添加一个新节点时(想想在 Windows 资源管理器中右键单击一个文件夹并说“新建 -> 文件夹”),新创建的节点会被选中。
  • 这是一个错误:右侧的详细视图应更新以显示新节点。然而,事实并非如此。我必须先切换到另一个树节点,然后才能在详细视图中看到有关新节点的信息。

该错误很容易重现,并且在“发布”和“调试”构建配置中都会发生。但是:只要我在事件处理程序中设置断点(对于“添加新节点”菜单条目)并启用它,一切正常 =>Heisenbug!我不需要做任何真正的调试。断点命中后点击“继续”即可。

为了更好地理解,我制作了 a video 来说明正在发生的事情。

我能想到的解决这个问题的办法就是让线程(应用程序是单线程的)休眠几秒钟,但这不起作用。

对于我可以尝试确定问题原因的任何建议,我将不胜感激。或者也许有人猜到了原因?

我的目标是 3.5 框架并使用 x86 作为解决方案平台。树形列表控件来自DevExpress'WinForms控件; Visual Studio 版本是 2010。

谢谢


更新 3:问题已解决。问题是对详细视图的 Focus() 方法的调用没有触发 GotFocus 事件,这对于更新详细视图至关重要。 (我不知道为什么,可能它已经有了焦点。)但是,该方法并没有失败,它根本没有做任何事情。

现在我只关注另一个控件,然后再关注细节视图,仅此而已。调试期间一切正常的原因是从 Visual Studio 切换回我的应用程序正确地将焦点设置在详细视图上。

解决此问题的一个主要障碍是在 GotFocus 事件处理程序中设置断点是没有用的:每当您尝试从 Visual Studio 切换回应用程序时,GotFocus 事件都会重新触发,并且您会被卡住在一个无限循环中。欢迎就如何解决此问题发表评论。

不管怎样,我的具体问题已经解决了。

非常感谢所有回答或评论的人。这对我帮助很大。


更新 2:在我的代码中,我选择了树中新创建的节点。这会触发 FocusedNodeChangedEvent。在相应的事件处理程序中,我更新详细视图并调用其 Focus() 方法。

似乎在没有设置断点时会失败。我认为在调试期间触发正确更新的是详细视图自动获得焦点。


窗口消息

更新 1:这是 Eddy 的回答提供的窗口消息。 (一长串已删除的消息)

【问题讨论】:

  • 我必须承认,尽管视频对我来说并没有增加多少,但这是我更想看到的。向你致敬,先生
  • 视频+1(以及真正准确的问题描述) - Deutsche Gründlichkeit
  • 谢谢,伙计们。很高兴看到视频受到赞赏。
  • 树列表中的哪些事件会导致详细信息面板的更新,或者这一切都是由树列表本身在内部完成的?创建新节点似乎不会触发更新详细视图的事件,同时从窗口获取焦点并将其放回(切换到调试器)会。
  • Eddy,我已经用该信息更新了主帖。

标签: c# .net visual-studio


【解决方案1】:

我第一次看到支持 SO 的视频 :)

我认为右侧窗格或控制组需要调用Invalidate() - 我认为断点正在触发,因为它切换到VS,过度绘制了窗口。然后,当您再次切换回来时,它会强制所有内容重新绘制,从而使其正常工作。

【讨论】:

  • 不幸的是,调用 Invalidate() 并没有改变任何东西。
  • 你必须有一些代码来构建和显示右窗格中的控件 - 希望在它自己的方法中 - 在该方法中执行 Trace.WriteLine 以验证它是否在应该被调用时被调用是(即在正常的单击选择中,以及在添加新项目时)。如果不是第二种情况,那么这只是您用于选择新项目的代码的问题 - 可能没有触发正确的事件或其他什么
【解决方案2】:

当你有一个“heisenbug”时,你需要看看发生了什么,但要轻触一下,不会中断程序的流程。

我建议您对问题所在形成一个假设。然后,我将编辑代码以包含对Debug 类的各种方法的调用,这些方法可以测试假设或仅提供运行时信息以进行诊断。然后在调试模式下运行你的代码,看看你在输出中得到了什么。

如果您有一个更难以捉摸的“hiesenbug”事件仅在发布模式下发生,您将需要滚动您自己的日志记录机制来收集您的证据。

【讨论】:

    【解决方案3】:

    System.Diagnostics.Debugger.Break() 放入另一个窗口的更新例程中。然后当 bug 出现时,会触发一个运行时断点,您可以查看堆栈。

    【讨论】:

    • 感谢您的提示。但是,我的问题是没有调用另一个窗口的更新例程。在新节点被选中后,我在树中调用了 Break(),但情况保持不变:对 Break() 的调用以某种方式触发了详细视图的正确更新。错误消失了。
    【解决方案4】:

    问题似乎与没有调用正确的事件来触发和更新子面板有关。当到达断点时,您的应用程序失去焦点,而当您返回它时,它再次获得焦点(这与运行它不同)。为了检查哪些事件被调用,哪些事件未被调用以及这两种场景之间的区别是什么,请将以下代码添加到您的表单中以查看出现的所有窗口消息。

    protected override void WndProc(ref Message winmsg)
    {
      base.WndProc(ref winmsg);
      System.Diagnostics.Debug.WriteLine(winmsg.Msg + " - " + winmsg);
    }
    

    比较差异可能有助于您确定解决此问题的方法。

    【讨论】:

    • 现在这真的很有用。我比较了这些消息,这是我第一次发现程序运行时带断点和不带断点之间的区别。我还没有找到错误的原因,因为我不理解这些消息,但它们为进一步调查提供了一个起点。
    • 我概述的相当通用的方法的应用 +1
    猜你喜欢
    • 1970-01-01
    • 2021-11-11
    • 2011-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多