I've recently been encountering the dreaded UserPreferenceChanged Event UI freezing problem and subsequently working my way through the possible causes, such as:
- Invoking on individual Controls rather than the main application Form (see https://stackoverflow.com/a/3832728/868159)
- Creating Controls on non-UI thread.
- First initialisation of the SystemEvents static class (see https://stackoverflow.com/a/4078528/868159)
But the biggest issue I am having is reliably reproducing the freeze. I've found that you can get a similar reproduction by running the application in an RDP session, exiting the session without logging off, and then re-connecting to the RDP session (more often than not, this raises the OnThemeChanged event rather than the UserPreferenceChanged event). Using this method I managed to freeze the application fairly consistently. I then followed some of the above advice and corrected issues that I found. This seemed to fix the problem and I handed over to QA and using the above method they could not freeze either.
However, customers are still seeing the freezing issue. I am getting process dump files from them when it occurs and can see that the SystemEvents.OnUserPreferenceChanged event has been triggered.
mscorlib.dll!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext)
System.Windows.Forms.dll!System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle waitHandle)
System.Windows.Forms.dll!System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control caller, System.Delegate method, object[] args, bool synchronous)
System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke(System.Delegate method, object[] args)
System.dll!Microsoft.Win32.SystemEvents.SystemEventInvokeInfo.Invoke(bool checkFinalization, object[] args)
System.dll!Microsoft.Win32.SystemEvents.RaiseEvent(bool checkFinalization, object key, object[] args)
System.dll!Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(int msg, System.IntPtr wParam, System.IntPtr lParam)
System.dll!Microsoft.Win32.SystemEvents.WindowProc(System.IntPtr hWnd, int msg, System.IntPtr wParam, System.IntPtr lParam)
[Native to Managed Transition]
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData)
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context)
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context)
So my next thought is that the RDP method above is not a reliable reproduction of the issue, and therefore I need to re-create the UserPreferenceChanged event (WM_SETTINGCHANGE).
So I created a quick console application with the following (based on some code from pinvoke.net)
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageTimeout(
IntPtr windowHandle,
uint Msg,
IntPtr wParam,
IntPtr lParam,
SendMessageTimeoutFlags flags,
uint timeout,
out IntPtr result);
const uint WM_SETTINGCHANGE = 0x1A;
IntPtr innerPinvokeResult;
var HWND_BROADCAST = new IntPtr(0xffff);
var pinvokeResult = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero,
IntPtr.Zero, SendMessageTimeoutFlags.SMTO_NORMAL, 1000, out innerPinvokeResult);
Console.WriteLine(pinvokeResult);
Console.WriteLine(innerPinvokeResult);
Console.WriteLine(pinvokeResult == (IntPtr) 0 ? "Failed" : "Success");
This seemed to work in that it causes redraw (or refresh?) or explorer windows that are open on my machine.
To verify, I added a button to the application, which when clicked subscribes to the event:
SystemEvents.UserPreferenceChanged += (s, e) => MessageBox.Show("user pref");
So I click the button in the application to subscribe and then run my console app to trigger the WM_SETTINGCHANGE and it does cause the application to show the message box. I've therefore tried to use my console application as part of testing to try and reproduce the problem - but it doesn't cause UI freezing!
One thing I have noticed that if I put a breakpoint on the MessageBox.Show in the test event subscription then the stack trace is not as I expect to be. I expected it to be the same as the customers were getting. Instead it is:
TestingForm.AnonymousMethod__40(object o, Microsoft.Win32.UserPreferenceChangedEventArgs ev) Line 1041
[Native to Managed Transition]
mscorlib.dll!System.Delegate.DynamicInvokeImpl(object[] args)
System.dll!Microsoft.Win32.SystemEvents.SystemEventInvokeInfo.InvokeCallback(object arg)
[Native to Managed Transition]
mscorlib.dll!System.Delegate.DynamicInvokeImpl(object[] args)
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackDo(System.Windows.Forms.Control.ThreadMethodEntry tme)
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(object obj)
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallback(System.Windows.Forms.Control.ThreadMethodEntry tme)
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbacks()
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m)
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam)
[Native to Managed Transition]
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData)
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context)
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context)
So my question is: Why do I get a different stack trace; Is my console application really raising the UserPreferenceChange event correctly? If not, how can I reproduce it?
I am using .NET 4.0.