0

In a windows form application which changes the desktop layout as it closes, Windows Forms seems to hang while processing a WM_SETTINGCHANGE message. This seems to happen for any WM_SETTINGSCHANGE message received between the start and end of application shutdown.

Apart from adding an arbitrary delay, is there a way to avoid this hang?

Apparent timeline of events:

  1. On main thread, in closing the only Form, a change to the desktop layout is triggered (specifically due to removing an AppBar with SHAppBarMessage(ABM_REMOVE, ...))
  2. The main thread begins shutting down (disposing controls, form, etc...)
  3. The ".NET System Events" thread receives WM_SETTINGCHANGE and Invokes to the main thread
  4. Since the main thread has not shutdown, the test at WindowsFormsSynchronizationContext.Send:82 succeeds
  5. The ".Net System Events" thread enters a WaitHandle
  6. The main thread, having had its main form closed, exits the message loop and Main returns
  7. Hang

Call stacks at hang:

Main Thread (no .Net stack, only native)

win32u.dll!NtUserMsgWaitForMultipleObjectsEx()
combase.dll!CCliModalLoop::BlockFn(void * * ahEvent, unsigned long cEvents, unsigned long * lpdwSignaled) Line 2108
combase.dll!ClassicSTAThreadWaitForHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * pdwIndex) Line 54
combase.dll!CoWaitForMultipleHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * lpdwindex) Line 126
[Inline Frame] hostpolicy.dll!coreclr_t::shutdown(int *) Line 152
hostpolicy.dll!run_app_for_context(const hostpolicy_context_t & context, int argc, const wchar_t * * argv) Line 264
...snip...

.NET System Events thread

System.Private.CoreLib.dll!System.Threading.WaitHandle.WaitOneNoCheck(int millisecondsTimeout) Line 139
    at /_/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs(139)
System.Windows.Forms.dll!System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle waitHandle) Line 3967
    at /_/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs(3967)
System.Windows.Forms.dll!System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control caller, System.Delegate method, object[] args, bool synchronous) Line 7141
    at /_/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs(7141)
System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke(System.Delegate method, object[] args) Line 6587
    at /_/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs(6587)
System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback d, object state) Line 88
    at /_/src/System.Windows.Forms/src/System/Windows/Forms/WindowsFormsSynchronizationContext.cs(88)
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.SystemEventInvokeInfo.Invoke(bool checkFinalization, object[] args) Line 35
    at Microsoft.Win32\SystemEvents.cs(35)
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.RaiseEvent(bool checkFinalization, object key, object[] args) Line 850
    at Microsoft.Win32\SystemEvents.cs(850)
    locals:
        array[0] 
            ._delegate = System.Windows.Forms.VisualStyles.VisualStyleRenderer.OnUserPreferenceChanging
            ._syncCtx = System.Windows.Forms.WindowsFormsSynchronizationContext
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.WindowProc(nint hWnd, int msg, nint wParam, nint lParam) Line 961
    at Microsoft.Win32\SystemEvents.cs(961)
    locals:
        msg = 8218
        wParam = 0x2f
        lParam = 0
[Native to Managed Transition]
[Managed to Native Transition]
Microsoft.Win32.SystemEvents.dll!Interop.User32.DispatchMessageW.____PInvoke|210_0(Interop.User32.MSG* msg)
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.WindowThreadProc() Line 1038
    at Microsoft.Win32\SystemEvents.cs(1038)

Notes:

Mitch
  • 21,223
  • 6
  • 63
  • 86

1 Answers1

0

Use a custom ApplicationContext which ensures the SystemEvents thread is not currently hung before exiting:

Application.Run(new SystemEventsSafeAppContext(new Form1()));

internal class SystemEventsSafeAppContext : ApplicationContext
{
    public SystemEventsSafeAppContext(Form mainForm)
        : base(mainForm)
    {
    }

    [DllImport("user32.dll")]
    static extern void PostQuitMessage(int nExitCode);

    protected override void OnMainFormClosed(object sender, EventArgs e)
    {
        var syncCtx = SynchronizationContext.Current;
        SystemEvents.InvokeOnEventsThread(() =>
        {
            PostQuitMessage(0);
            syncCtx.Post((_) =>
            {
                ExitThread();
            }, null);
        });
    }
}
Mitch
  • 21,223
  • 6
  • 63
  • 86
  • I'm glad you have got your solution and thank for your sharing. I would appreciate it if you mark them as answer and this will be beneficial to other community. – Jeaninez - MSFT Aug 18 '23 at 08:42
  • @Jeaninez-MSFT, the solution I gave is at best a dirty hack. `QUIT`ing threads out from under winforms. I'm hoping a proper solution shows up either here or from Microsoft support. – Mitch Aug 21 '23 at 15:48