WinForms application hang due to SystemEvents.OnUserPreferenceChanged event

I've been dealing with a strange application hang in my clients installations. After trying several things i reached the conclusion that without a dump it won't work. So I've taken a dump from one of my clients at the hang time. I must note that it only happens in my installations and not in my development computers.

In my dump I see that the SystemEvents.OnUserPreferenceChanged event causes my UI thread to block waiting for a thread, that is no longer pumping messages.

After searching here and in google, I found that some people we're having that issue as well. I went up and down my code looking to see if by any chance we created a control or a form on a non UI thread, but with no luck.

This is my !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] 

I tought that maybe using the dump i can figure out what is the control that was created on the other thread and it would give me a clue.

I tried !dso to get all of the stack objects:

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[])

The only thing i see is InsFocusBI.Presentation.Controls.CustomListView but i don't see that it is created on another thread.

Any advice? can someone give another idea or something to try?

Thanks


Solution 1:

The freezing issue due to SystemEvents.OnUserPreferenceChanged is quite common bug and here is a Microsoft explanation and recommendation how to fix it.

Here is the function you can invoke any time (before or even after freeze) to find out which particular controls subscribed to SystemEvents was created on wrong threads and thus could freeze your app:

    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}");
        }
    }

Solution 2:

Well, you have thee classic problem of control 'creation' on non UI thread.

  • It may be you are creating the control.
  • Access to some property creates a control underneath without you knowing (rare).
  • Access to the Handle property before it is been created (may be indirectly).
  • Fooled by InvokeRequired (ref 1), (ref 2).

Great read on the subject: (ref 1) (ref 2) and (ref 3).

I don't know of a way to find the control in a dump (tried myself) but you can try to set a breakpoint in code as described in (ref 4) and (ref 5).

Cheers.

Reference:

  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

Solution 3:

Following Vlad code, I have found a way to unsubscribe the system events on all objects subscribed out of the main UI thread.

This code works for me and it is solving years of pain dealing with systemevents:

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
    }
}