Playback UserPreferenceChanged event to check for fixed freeze issue

I recently ran into the dreaded UserPreferenceChanged Event UI freeze issue and subsequently worked the way through for possible reasons like:

  • Calling individual controls, not the main form of the application (see StackOverflow question )
  • Create controls for non-UI threads.
  • First initialization of the static SystemEvents class (see this StackOverflow question )

But the biggest problem I am having is reliably reproduce the freeze. I found that you can get a similar replay by starting the application in an RDP session, logging out of the session without logging out, and then reconnecting to the RDP session (most often this triggers the OnThemeChanged event rather than the UserPreferenceChanged event). Using this method, I was able to freeze the application pretty quickly. Then I followed some of the tips above and fixed the problems I found. This seemed to fix the problem and I passed the QA and using the above method, they couldn't freeze either.

However, customers still see the problem of freezing. I am getting process dump files from them when this happens and can see that the SystemEvents.OnUserPreferenceChanged event has been fired.

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 above RDP method is not a reliable reproduction of the problem, so I need to re-create the UserPreferenceChanged (WM_SETTINGCHANGE) event.

So I created a quick console app 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");

      

It seemed to work because it triggers a redraw (or refresh?) Or explorer windows that open on my machine.

To test, I added a button to the app that subscribes to the event when clicked:

SystemEvents.UserPreferenceChanged += (s, e) => MessageBox.Show("user pref");

      

So, I click a button in my subscription app and then I launch a console app to start WM_SETTINGCHANGE and that makes the app display a message box. So I tried using a console app as part of my testing to try and reproduce the problem, but that doesn't cause the UI to freeze!

One thing I noticed is that if I put a breakpoint on MessageBox.Show in subscribing to a test event, then the stack trace is not what I expect. I expected it to be the same as clients. Instead of this:

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 am I getting a different stack trace; Is my console app actually raising the UserPreferenceChange event correctly? If not, how to reproduce it?

I am using .NET 4.0.

+3


source to share





All Articles