winforms `OnUserPreferenceChanging`挂起,不使用后台控件

snvhrwxg  于 2023-10-23  发布在  其他
关注(0)|答案(1)|浏览(126)

在关闭时更改桌面布局的windows form application中,Windows窗体在处理WM_SETTINGCHANGE消息时似乎挂起。这似乎发生在应用程序关闭开始和结束之间收到的任何WM_SETTINGSCHANGE消息。

除了添加任意延迟之外,还有什么方法可以避免这种挂起?

事件的明显时间轴:
1.在主线程上,在关闭唯一的窗体时,会触发对桌面布局的更改(特别是由于使用SHAppBarMessage(ABM_REMOVE, ...)删除AppBar)
1.主线程开始关闭(释放控件、窗体等)
1.“.NET系统事件”线程接收WM_SETTINGCHANGE并返回到主线程
1.由于主线程没有关闭,WindowsFormsSynchronizationContext.Send:82测试成功
1.“.Net System Events”线程进入WaitHandle
1.主线程关闭了主窗体,退出消息循环,Main返回
1.挂
挂起时的调用堆栈:
主线程(无.Net堆栈,仅限本机)

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.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)

备注:

gab6jxml

gab6jxml1#

was a bug在.Net中从.Net 5到.Net 7.0.11。.Net Framework不受影响。要解决这个问题,请至少升级到.Net 7.0.12或使用下面的PostQuitMessage黑客。
使用自定义的ApplicationContext,确保SystemEvents线程在退出前当前未挂起:

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

相关问题