【问题标题】:WinForms application hang due to SystemEvents.OnUserPreferenceChanged event由于 SystemEvents.OnUserPreferenceChanged 事件,WinForms 应用程序挂起
【发布时间】:2012-05-13 07:10:11
【问题描述】:

我一直在处理我的客户端安装中出现的一个奇怪的应用程序挂起问题。 在尝试了几件事后,我得出的结论是,没有转储它就行不通。 所以我在挂起时从我的一位客户那里得到了转储。 我必须注意,它只发生在我的安装中,而不发生在我的开发计算机中。

在我的转储中,我看到 SystemEvents.OnUserPreferenceChanged 事件导致我的 UI 线程阻塞等待一个不再发送消息的线程。

在这里和谷歌搜索后,我发现有些人也遇到了这个问题。 我在代码中上下移动,看看我们是否有机会在非 UI 线程上创建了控件或表单,但没有运气。

这是我的 !clrstack

 0012ee5c 7c90e514 [HelperMethodFrame_1OBJ: 0012ee5c] 
System.Threading.WaitHandle.WaitOneNative(Microsoft.Win32.SafeHandles.SafeWaitHandle, UInt32, Boolean, Boolean)
0012ef08 792b68af System.Threading.WaitHandle.WaitOne(Int64, Boolean)
0012ef24 792b6865 System.Threading.WaitHandle.WaitOne(Int32, Boolean)
0012ef38 7b6f1a4f System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
0012ef4c 7ba2d68b System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
0012efec 7b6f33ac System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
0012f020 7b920bd7 System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback, System.Object)
0012f038 7a92ed62 Microsoft.Win32.SystemEvents+SystemEventInvokeInfo.Invoke(Boolean, System.Object[])
0012f06c 7a92dc8f Microsoft.Win32.SystemEvents.RaiseEvent(Boolean, System.Object, System.Object[])
0012f0b8 7a92e227 Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(Int32, IntPtr, IntPtr)
0012f0d8 7aaa06ec Microsoft.Win32.SystemEvents.WindowProc(IntPtr, Int32, IntPtr, IntPtr)
0012f0dc 003c222c [InlinedCallFrame: 0012f0dc] 
0012f2a0 7b1d8d2e System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32, Int32, Int32)
0012f33c 7b1d8997 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
0012f390 7b1d87e1 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
0012f3c0 7b195931 System.Windows.Forms.Application.Run(System.Windows.Forms.Form)
0012f3d4 034608b6 InsFocusBI.Presentation.MdiMain.NewMdiMainHandler(System.Object, NewSingleInstanceEventArgs)
0012f3ec 034607ac InsFocusBI.Utilities.SingleInstanceHandler.Start(System.String[], System.String)
0012f42c 0346021a InsFocusBI.Presentation.MdiMain.Run(System.String[])
0012f440 0346019b InsFocusBI.Presentation.MdiMain.Main(System.String[])
0012f688 79e71b4c [GCFrame: 0012f688] 

我认为也许使用转储我可以弄清楚在另一个线程上创建的控件是什么,这会给我一个线索。

我尝试 !dso 来获取所有堆栈对象:

OS Thread Id: 0x4f0 (0)
ESP/REG  Object   Name
0012ed90 0132e8cc System.Windows.Forms.WindowsFormsSynchronizationContext
0012ee1c 06bfe2a0 System.Threading.ManualResetEvent
0012ee30 06bfe2a0 System.Threading.ManualResetEvent
0012ee9c 06bfe2a0 System.Threading.ManualResetEvent
0012eea4 0132381c System.Collections.Hashtable
0012eeb0 06bfe2a0 System.Threading.ManualResetEvent
0012eee0 06bfe2b8 Microsoft.Win32.SafeHandles.SafeWaitHandle
0012ef28 06bfe2a0 System.Threading.ManualResetEvent
0012ef78 06b4d080 System.Windows.Forms.PropertyStore
0012ef80 06b4d018 System.Windows.Forms.Application+MarshalingControl
0012ef88 06b4d018 System.Windows.Forms.Application+MarshalingControl
0012ef8c 06bfe1b0 System.Windows.Forms.Control+ThreadMethodEntry
0012ef90 06b4d018 System.Windows.Forms.Application+MarshalingControl
0012ef94 06b4d018 System.Windows.Forms.Application+MarshalingControl
0012ef9c 06b4d018 System.Windows.Forms.Application+MarshalingControl
0012efa4 06b4d018 System.Windows.Forms.Application+MarshalingControl
0012efd4 06b4d018 System.Windows.Forms.Application+MarshalingControl
0012efe4 06bfe124 System.Object[]    (System.Object[])
0012efe8 06bfe104 System.Threading.SendOrPostCallback
0012efec 06bfe138 System.Windows.Forms.Control+MultithreadSafeCallScope
0012f004 064a8228 System.Threading.Thread
0012f00c 06b4d018 System.Windows.Forms.Application+MarshalingControl
0012f01c 06bfe124 System.Object[]    (System.Object[])
0012f028 06b4cf64 System.Windows.Forms.WindowsFormsSynchronizationContext
0012f034 06bfddc4 System.Object[]    (System.Object[])
0012f038 06b6096c Microsoft.Win32.SystemEvents+SystemEventInvokeInfo
0012f068 06bfddc4 System.Object[]    (System.Object[])
0012f074 0132a174 System.Object
0012f078 06bfdde8 System.Object[]    (Microsoft.Win32.SystemEvents+SystemEventInvokeInfo[])
0012f07c 0132a298 System.Object
0012f0a4 0132a3c4 Microsoft.Win32.SystemEvents
0012f0a8 0132a298 System.Object
0012f0b4 06bfddc4 System.Object[]    (System.Object[])
0012f0c0 0132a3c4 Microsoft.Win32.SystemEvents
0012f270 017dbd10 InsFocusBI.Presentation.Controls.CustomListView
0012f288 0132e8f0 System.Windows.Forms.Application+ThreadContext
0012f2cc 017860c0 System.Windows.Forms.NativeMethods+MSG[]
0012f2d0 0132e8f0 System.Windows.Forms.Application+ThreadContext
0012f2d8 01372050 System.Windows.Forms.Application+ComponentManager
0012f350 0132e8f0 System.Windows.Forms.Application+ThreadContext
0012f38c 01785a74 System.Windows.Forms.ApplicationContext
0012f428 012fd464 System.String    el02
0012f6f4 012f913c System.Object[]    (System.String[])

我唯一看到的是 InsFocusBI.Presentation.Controls.CustomListView 但我没有看到它是在另一个线程上创建的。

有什么建议吗?有人可以提出其他想法或尝试吗?

谢谢

【问题讨论】:

    标签: c# .net winforms


    【解决方案1】:

    由于 SystemEvents.OnUserPreferenceChanged 导致的冻结问题是相当常见的错误,here 是微软的解释和建议如何修复它。

    这是您可以随时(在冻结之前甚至之后)调用的函数,以找出订阅 SystemEvents 的哪些特定控件是在错误的线程上创建的,因此可能会冻结您的应用:

        private static void CheckSystemEventsHandlersForFreeze()
        {
            var handlers = typeof(SystemEvents).GetField("_handlers", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
            var handlersValues = handlers.GetType().GetProperty("Values").GetValue(handlers);
            foreach (var invokeInfos in (handlersValues as IEnumerable).OfType<object>().ToArray())
            foreach (var invokeInfo in (invokeInfos as IEnumerable).OfType<object>().ToArray())
            {
                var syncContext = invokeInfo.GetType().GetField("_syncContext", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(invokeInfo);
                if (syncContext == null) throw new Exception("syncContext missing");
                if (!(syncContext is WindowsFormsSynchronizationContext)) continue;
                var threadRef = (WeakReference) syncContext.GetType().GetField("destinationThreadRef", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(syncContext);
                if (!threadRef.IsAlive) continue;
                var thread = (Thread)threadRef.Target;
                if (thread.ManagedThreadId == 1) continue;  // Change here if you have more valid UI threads to ignore
                var dlg = (Delegate) invokeInfo.GetType().GetField("_delegate", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(invokeInfo);
                MessageBox.Show($"SystemEvents handler '{dlg.Method.DeclaringType}.{dlg.Method.Name}' could freeze app due to wrong thread: "
                                + $"{thread.ManagedThreadId},{thread.IsThreadPoolThread},{thread.IsAlive},{thread.Name}");
            }
        }
    

    【讨论】:

    • 您可能要注意,当您真正需要它时,您不能再点击按钮了 :)
    • 您可以在冻结前的任何时间(通过单击)调用此函数。或者可以通过计时器定期调用它,例如每分钟一次。如果冻结,它应该从另一个线程工作。
    • “坏”处理程序通常在冻结之前很久就添加到 SystemEvents 中:)
    • 并在冻结前删除。最坏的情况是 SystemEvents 初始化错误,猜测错误的 UI 线程。一个不只是显示一个简单的位图的闪屏就可以做到这一点。由于初始化线程不再存在,它现在在线程池线程上触发事件。我认为您的诊断无法证明这一点。
    • 你是个好人,你真的帮助了我!!,我运行它,发现“System.Windows.Forms.RichTextBox”是问题所在。我正在创建它以在应用程序的角落生成 RTF。一旦它在无 ui 线程中执行......并且系统在下一个系统事件上挂起
    【解决方案2】:

    嗯,你遇到了在非 UI 线程上“创建”控件的经典问题。

    • 可能是您正在创建控件。
    • 访问某些属性会在您不知情的情况下创建一个控件(很少见)。
    • 在创建之前访问 Handle 属性(可能是间接的)。
    • 被 InvokeRequired 愚弄(参考 1),(参考 2)。

    关于该主题的精彩阅读:(参考 1)(参考 2)和(参考 3)。

    我不知道如何在转储中找到控件(我自己试过) 但您可以尝试在 (ref 4) 和 (ref 5) 中描述的代码中设置断点。

    干杯。

    参考:

    1. Mysterious Hang or The Great Deception of InvokeRequired

    2. Control.Trifecta: InvokeRequired, IsHandleCreated, and IsDisposed

    3. .NET 2.0 WinForms multithreading and a few long days

    4. Debugging UI

    5. The case of the leaking thread handles

    【讨论】:

      【解决方案3】:

      按照 Vlad 代码,我找到了一种方法来取消订阅所有从主 UI 线程订阅的对象的系统事件。

      这段代码对我有用,它解决了多年来处理系统事件的痛苦:

      public static void UnsubscribeSystemEvents()
      {
          try
          {
              var handlers = typeof(SystemEvents).GetField("_handlers", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
              var handlersValues = handlers.GetType().GetProperty("Values").GetValue(handlers);
              foreach (var invokeInfos in (handlersValues as IEnumerable).OfType<object>().ToArray())
                  foreach (var invokeInfo in (invokeInfos as IEnumerable).OfType<object>().ToArray())
                  {
                      var syncContext = invokeInfo.GetType().GetField("_syncContext", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(invokeInfo);
                      if (syncContext == null) 
                          throw new Exception("syncContext missing");
                      if (!(syncContext is WindowsFormsSynchronizationContext))
                          continue;
                      var threadRef = (WeakReference)syncContext.GetType().GetField("destinationThreadRef", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(syncContext);
                      if (!threadRef.IsAlive)
                          continue;
                      var thread = (System.Threading.Thread)threadRef.Target;
                      if (thread.ManagedThreadId == 1)
                              continue;  // Change here if you have more valid UI threads to ignore
                      var dlg = (Delegate)invokeInfo.GetType().GetField("_delegate", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(invokeInfo);
                      var handler = (UserPreferenceChangedEventHandler)Delegate.CreateDelegate(typeof(UserPreferenceChangedEventHandler), dlg.Target, dlg.Method.Name);
                      SystemEvents.UserPreferenceChanged -= handler;
                  }
          }
          catch ()
          {                
              //trace here your errors
          }
      }
      

      【讨论】:

        【解决方案4】:

        如果您还不知道如何检查这一点。查看此页面Kim Greenlee blog 并尝试使用 spy++ 解决方案。也许它对你有帮助,我们也有同样的问题,无法模拟挂起的应用程序的问题,但仍在研究它!

        【讨论】:

          【解决方案5】:

          我不得不在一个大型应用程序中遇到这个错误。错误的代码可能很难找到,因为错误的原因和影响可能相隔几分钟甚至几小时。

          我稍微更改了 Vlad Rudenko 的答案,以便它提供堆栈跟踪。这对我来说帮助很大。希望它可以帮助某人

          UI Freeze caused by WindowsFormsSynchronizationContext and System.Events.UserPreferenceChanged

          【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-03-04
          • 2012-11-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-13
          相关资源
          最近更新 更多