【问题标题】:.NET 4.0 and the dreaded OnUserPreferenceChanged Hang.NET 4.0 和可怕的 OnUserPreferenceChanged 挂起
【发布时间】:2010-11-02 12:55:58
【问题描述】:

我一直被 Ivan Krivyakov 很好地提到的可怕的 OnUserPreferenceChanged Hang 所困扰,在这里:

http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html#BeginInvokeDance

我前一阵子发了一个问题,当我最初遇到这个问题时:

Yet another C# Deadlock Debugging Question

我以为我已经通过删除从 UI 线程构建的控件解决了这个问题,但过了一会儿它又出现了(可能永远不会离开......)。

我们一直在使用 .NET 3.5,据我了解,它使用的是 CLR 2.0。最近,应用程序已升级为使用 .NET 4.0 Client Profile / CLR 4.0。此外,我们已从 Infragistics WinForms 10.1 升级到 10.3。唯一的其他区别是之前的版本被混淆了......有人遇到过混淆和挂起的问题吗?

我再次尝试一劳永逸地摆脱任何应用程序挂起,但不同寻常的是,我无法在最新版本中重现该挂起(使用 .NET 4.0)。在以前的版本(使用 .NET 3.5)中,使用 Ivan Krivyakov 的方便的 Freezer 应用程序(参见他的文章)很容易重现该挂起,该应用程序会根据请求触发 WM_SETTINGCHANGE 消息。

我可能有点希望这个问题会自行消失,但有谁知道 CLR 从 2.0 到 4.0 是否有任何更改会导致这种情况?

------------------------------------------ - - - - -解决方案 - - - - - - - - - - - - - - - - - - - - ----------

所以在测试应用程序的变体之后,例如CLR 2.0 + Infragistics 2010.1、CLR 2.0 + Infragistics 2010.3 和 CLR 4.0 + Infragistics 2010.1,我们认为我们已经确定问题是 WinForms 2010.1 中的 Infragistics 组件存在问题(无热修复)。我们还没有使用 CLR 2.0 或 CLR 4.0 和 Infragistics 2010.3 来重现冻结(而且我们现在已经非常擅长重现这种情况......)。

【问题讨论】:

标签: c# .net multithreading clr deadlock


【解决方案1】:

从 UI 线程构造的控件...

是的,这是触发此问题的好方法。根本问题是由 SystemEvents 类引起的,它具有在正确线程上引发其事件的令人羡慕的任务。 UserPreferenceChanged 事件是典型的麻烦制造者,许多控件订阅它,因此当用户更改桌面主题时它们可以重新绘制自己。组件供应商不会忽视对此的需求。工具箱中的标准 .NET 框架控件也没有。

测试此问题的一种通常不错的方法是锁定工作站(按 Win+L 键),这也是用户机器上通常触发死锁的方法。切换到安全桌面往往会触发该事件。额外的怪癖是,当您调试程序时,这永远不会发生,并且它具有与时间相关的棘手行为,因为这往往会在没有人在机器上时发生。调试起来特别困难。

遇到此类问题的一种标准方法是程序中的初始化问题。订阅的第一个 SystemEvents 事件导致 SystemEvents 类初始化自身并设置接收这些通知和引发其相应事件所需的管道。一个自定义的启动画面做的太多(即不仅仅是显示一个位图)并且在标记为 STA 的工作线程上运行就足以导致这个错误。像 ProgressBar 这样简单的东西已经足够了。 SystemEvents 假设工作线程是程序的主线程,现在可以很容易地在将来在错误的线程上生成事件。对此有一个很好的诊断,如果该工作线程不再存在,则会产生第一次机会异常。您可以在“输出”窗口中看到它。

或者您创建另一个 UI 线程并在两个线程上都有表单。不可避免地,其中一种形式总是会在错误的线程上获取事件。

唯一体面的建议是承认在工作线程上创建 UI 是火箭科学,微软也不知道如何正确地做到这一点。值得注意的是,.NET 1.x 控件有一个事件处理程序,当它从错误的线程调用时仍然可以正常工作,它只是调用 Control.Invalidate()。但这是在 2.0 中似乎已经丢失的知识,ToolStrip 是good example。并且不要相信组件供应商能做到这一点,尤其是 Infragistics 并没有很好的声誉。不要这样做。

【讨论】:

  • 感谢汉斯提供的令人耳目一新的信息!我们确实有一个启动画面,它是通过对 Application.Run() 的单独调用创建的,我无法理解它会给它自己的消息泵……只要我们将任何调用编组到启动画面中,它就会是安全的。那么这是不正确的吗?我不知道有一些内置的.NET 支持闪屏。我去看看。
  • 另外,您知道为什么我无法使用使用 CLR 4.0 的应用程序的未混淆版本来重现此挂起吗?
  • 死锁对时间非常敏感。发布版本中的时间与调试版本不同。听起来您对引发此问题的位置没有太多了解。我推荐我提出的解决方法。
  • 感谢汉斯的帮助。我们的多线程启动画面有点乱,但在 jmcilhinney 建议的实现的帮助下:vbforums.com/showthread.php?p=3896435,现在它更干净了。然而,它并没有解决问题。在尝试了仍在使用 CLR 2.0 的最新版本的 Infragistics 之后,我们相信我们已经确定这是 Infragistics 组件中的错误。如果它发生变化,我会在这里发回……无论如何,谢谢你的帮助,汉斯 - 你至少帮助清理了我们的代码 =)。
  • 只是为了确认任何 UI,甚至与调用后台线程的表单(例如自定义表单)无关,都会导致应用程序挂起。在我的情况下,我显示了一个对话框,用于选择一些稍后用于执行批处理作业的值,所有这些都在后台线程中。我在远程桌面会话中注意到这一点,每次会话中断时,应用程序都会冻结。从线程重新定位对话框后,一切正常。结论:忘记后台线程中的任何 UI。顺便说一句,我在 .Net 4.6.1 上。
【解决方案2】:

我找到的解决此问题的最佳指南在这里:

它会引导您使用 WinDbg 来验证错误的原因,并向您展示如何找到导致错误的原因。正如您所提到的,这很可能是在非 ui 线程上创建的控件引起的。

在我的例子中,我通过创建一个工厂解决了这个问题,该工厂使用 UI 线程中的 SynchronizationContext 创建控件,然后我调用 CreateControl() 以强制创建 UI 句柄。

微软支持文章在这里:

【讨论】:

    【解决方案3】:

    如果您从他的网页上运行示例应用程序,首先是 CLR 2.0,然后是 CLR 4.0,那么您会注意到问题在 4.0 中似乎真的消失了 - 不知道发生了什么变化,但也许他们真的解决了这个问题。 BR

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-02-02
      • 2011-03-24
      • 2020-11-25
      • 2018-08-23
      • 1970-01-01
      • 2010-12-24
      相关资源
      最近更新 更多