WinForms application hang due to SystemEvents.OnUserPreferenceChanged event

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

Leave a Comment