代码之家  ›  专栏  ›  技术社区  ›  Hari

C#Winforms应用程序挂起而不是引发异常

  •  3
  • Hari  · 技术社区  · 12 年前

    昨天我遇到了一个非常奇怪的错误,一天后我几乎没有任何进展,所以我想这是一个很好的向社区询问的候选人。我会请一些病人,因为我认为这是一个棘手的问题。

    我有一个C#Winforms应用程序,在生产过程中点击几下就会挂起。这种情况在开发环境中永远不会发生,只有在生产环境中才会发生。当挂起发生时,什么都没有发生(没有错误消息,但根据任务管理器,任务进入“无响应”状态),但GUI变得不负责任。我在同一个环境中尝试过,我可以确认这种行为。

    不幸的是,无法在prod-env中安装开发工具和调试应用程序。我能做的最好的事情就是在应用程序停止时从中转储内存。问题是我完全不明白我在崩溃转储中看到了什么:我的主线程(GUI线程)似乎被一条指令卡住了,我找不到任何原因。

    这是我的主线程的堆栈跟踪:

    KERNELBASE.dll!_RaiseException@16()  + 0x54 bytes    
    [External Code]    
    CFAPControlLibrary.dll!CFAPControlLibrary.Communication.Base.GetSetting(string settingName) Line 850 + 0x10 bytes    C#
    CFAPControlLibrary.dll!CFAPControlLibrary.ConfigHelper.Get<CFAPControlLibrary.DataTypes.ActionSortingOption>(string settingName) Line 25 + 0x35 bytes    C#
    CFAPControlLibrary.dll!CFAPControlLibrary.ConfigHelper.Get<CFAPControlLibrary.DataTypes.ActionSortingOption>(string settingName, CFAPControlLibrary.DataTypes.ActionSortingOption defaultVal) Line 15 + 0x9 bytes    C#    CFAPControlLibrary.dll!CFAPControlLibrary.DataTypes.ActionStorage.Sort(System.Collections.Generic.List<CFAPControlLibrary.DataTypes.ActionClass> subject) Line 167 + 0xe bytes    C#
    CFAPControlLibrary.dll!CFAPControlLibrary.DataTypes.ActionStorage.GetByStatus(string pStatus) Line 162 + 0x46 bytes    C#
    CFAPControlLibrary.dll!CFAPControlLibrary.ActionSelector.FillNodes() Line 48 + 0x26 bytes    C#
    CFAPControlLibrary.dll!CFAPControlLibrary.CFAPMain.OnActionDetailsArrived(CFAPControlLibrary.CFAPMain.RawActionDetails bwr) Line 371 + 0x10 bytes    C#
    CFAPControlLibrary.dll!CFAPControlLibrary.CFAPMain.OnGetDetailsCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) Line 337 + 0xb bytes    C#
    user32.dll!_InternalCallWinProc@20()  + 0x23 bytes    
    user32.dll!_UserCallWinProcCheckWow@32()  + 0xb3 bytes    
    user32.dll!_DispatchMessageWorker@8()  + 0xe6 bytes    
    user32.dll!_DispatchMessageW@4()  + 0xf bytes    
    [External Code]    
    CFAPHost.exe!CFAPHost.Program.Main(string[] args) Line 50 + 0x1d bytes    C#
    [External Code]    
    mscoreei.dll!__CorExeMain@0()  + 0x38 bytes    
    mscoree.dll!_ShellShim__CorExeMain@0()  + 0x227 bytes    
    mscoree.dll!__CorExeMain_Exported@0()  + 0x8 bytes    
    kernel32.dll!@BaseThreadInitThunk@12()  + 0x12 bytes    
    ntdll.dll!___RtlUserThreadStart@8()  + 0x27 bytes    
    ntdll.dll!__RtlUserThreadStart@8()  + 0x1b bytes
    

    下面是我从顶部堆栈框架中获得的源代码: 从KernelBase.dll进行的反汇编: Frame from KernelBase.dll

    与我代码中的最后一帧相比,m_SettingCache是一个Dictionary,它不包含请求的密钥: Base.GetSetting

    接下来的几帧: Frame from KernelBase.dll Frame from KernelBase.dll Frame from KernelBase.dll

    我认为代码非常简单——它只是带有默认值的通用设置。如果出现问题(设置名称未定义或无法转换),将返回默认值。这个代码确实有效。我从转储中看到的是,从字典中读取的内容永远不会返回,尽管它应该抛出KeyNotFoundException,但这种情况从未发生过。有什么建议吗?

    注意:主线程确实在转储捕获的状态下停止:每次进行转储时,结果都是一样的。

    注意2:挂起从来不会发生在这个代码路径的第一次执行时,在每个场景中,这个完全相同的代码路径都是在挂起之前执行的(从应用程序日志推断)

    我将根据要求提供更多细节。 提前谢谢。

    编辑:

    CFAPControlLibrary.dll是应用程序的主程序集。它包含窗口窗体及其相应的逻辑。与服务器的通信是通过WCF实现的。更大的请求是在使用BackgroundWorker的并行线程中发出的。您在调用堆栈中看到的执行路径是由这样一个BackgroundWorker的completion事件调用的。

    我粘贴了请求的代码位 here

    我的AppDomain.CurrentDomain.UnhandledException处理程序为 here

    堆栈中我首先认为不重要但后来被证明很重要的部分(从图像中删除敏感字符串文字):

    Evidence for Application.Run 这表明Application.Run被调用了,我不知道为什么它没有显示在调用堆栈中。

    使现代化

    在花了三天时间没有找到问题的原因后,我决定尝试一种变通方法。由于内存转储显示应用程序总是挂在同一点:当KeyNotFound异常应该被抛出时。最简单的解决方法是重构代码,尽可能不抛出。那个版本通过了测试,从未挂起。 这根本不是一个解决方案,但我们不能再在这上面花时间了。所以基本上我会交叉手指发送代码,希望我再也不会看到这种崩溃。

    谢谢你的所有建议

    1 回复  |  直到 7 年前
        1
  •  5
  •   Hans Passant    12 年前
    user32.dll!_DispatchMessageW@4()  + 0xf bytes    
    [External Code]    
    CFAPHost.exe!CFAPHost.Program.Main(string[] args) Line 50 + 0x1d bytes    C#
    

    重写堆栈跟踪的这一部分出现了严重错误。Main()方法应该 总是 调用Application.Run()来启动消息循环。或者应该存在ShowDialog()调用,这是发送消息的两种正常方式。两者都不存在,但是无论如何都会调用DispatchMessage()winapi函数。

    还有一种非常模糊的其他方式可以在CLR中注入消息。当应用程序使用 语句,就像GUI应用程序的主线程一样。或者WaitHandle.WaitOne()或Thread.Join(),这是其他常见的阻塞方法。阻塞STA线程是非法的,因为它很可能会导致死锁,因此CLR会泵送以避免出现问题。执行此操作的代码将隐藏在[外部代码]部分中。

    在发布的代码中肯定有证据表明,它使用 在非常不合适的地方。使用 UI中的代码永远不会正确。

    应用程序崩溃时出现死锁也很容易解释。

    这是代码中的一个严重结构问题,你需要修复它。从Main()方法开始,这个问题很早就出现了。在开发机器上也很容易检查,只需查看调用堆栈即可。