You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Appbar时.NET 6/7应用无法正常关闭的原因排查

.NET 6/7中AppBar导致应用无法正常退出的问题

问题现象

  • 代码执行到Program.Main末尾(Application.Run(new Form1());执行完成),但应用仍处于运行状态。
  • 在Program.Main末尾调用Environment.Exit(0);时,该调用会挂起。
  • 若表单构造函数执行后无子控件,则不会出现此问题。
  • 该问题在.NET Framework 4.8中不存在,但在.NET 6及.NET 7中会出现。
  • 在Program.Main末尾添加Process.GetCurrentProcess().Kill();可强制终止进程,但属于暴力解决方式。
  • 注释Controls.Add(q)代码也可避免此问题。

复现代码

【警告】运行此代码后请关闭窗口,确保SHAppBarMessage((UInt32)AppBarMessages.Remove, ref msgData);得以执行,否则可能导致部分屏幕区域无法正常使用,需重启恢复。

public partial class Form1 : Form
{
    [DllImport("shell32.dll")]
    public static extern UInt32 SHAppBarMessage(UInt32 dwMessage, ref APPBARDATA pData);

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public Int32 left;
        public Int32 top;
        public Int32 right;
        public Int32 bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct APPBARDATA
    {
        public UInt32 cbSize;
        public IntPtr hWnd;
        public UInt32 uCallbackMessage;
        public UInt32 uEdge;
        public RECT rc;
        public Int32 lParam;
    }

    public enum AppBarMessages
    {
        New = 0x00,
        Remove = 0x01,
        QueryPos = 0x02,
        SetPos = 0x03,
        GetState = 0x04,
        GetTaskBarPos = 0x05,
        Activate = 0x06,
        GetAutoHideBar = 0x07,
        SetAutoHideBar = 0x08,
        WindowPosChanged = 0x09,
        SetState = 0x0a
    }

    Control q;
    public Form1()
    {
        InitializeComponent();

        APPBARDATA msgData = new APPBARDATA();
        msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
        msgData.hWnd = Handle;
        msgData.uCallbackMessage = 0x410; //0x400 WM_User

        SHAppBarMessage((UInt32)AppBarMessages.New, ref msgData);

        msgData = new APPBARDATA();
        msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
        msgData.hWnd = Handle;
        msgData.uEdge = (UInt32)1; //ABE_Top
        msgData.rc = new RECT()
        {
            left = 0,
            right = SystemInformation.PrimaryMonitorSize.Width,
            bottom = 50,
            top = 0
        };


        SHAppBarMessage((UInt32)AppBarMessages.SetPos, ref msgData);

        q = new Button();
        Controls.Add(q);
    }

    protected override void OnClosing(CancelEventArgs e)
    {
        APPBARDATA msgData = new APPBARDATA();
        msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
        msgData.hWnd = Handle;
        SHAppBarMessage((UInt32)AppBarMessages.Remove, ref msgData);
        base.OnClosing(e);
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);
    }
}

问题原因

  1. .NET 6+ WinForms生命周期逻辑变更:与.NET Framework相比,.NET 6+对WinForms的托管线程、消息循环和资源回收机制做了调整。当通过SHAppBarMessage注册AppBar后,系统Shell会持有窗口句柄的引用;而子控件的存在会改变窗口资源的释放顺序,导致部分未处理的系统消息或托管资源没有被正确回收,进程无法正常终止。
  2. AppBar注销时机与资源释放不匹配:当前代码在OnClosing中执行AppBar注销,但此时窗口和子控件的资源尚未开始销毁,Shell可能仍然持有窗口句柄的引用。.NET Framework的句柄回收机制会更快速地释放这类资源,而.NET 6+的优雅回收逻辑会等待所有资源释放完成,进而导致进程挂起。
  3. Environment.Exit挂起的本质:Environment.Exit会触发.NET运行时的优雅终止流程,尝试清理所有托管线程和资源,但由于Shell仍持有窗口相关资源的引用,终止过程被阻塞;而Process.Kill()是强制终止进程,绕过了优雅清理逻辑,因此能强制结束进程,但属于不规范的解决方式。

正确解决方法

调整资源释放顺序,先清空子控件资源,再在窗口销毁完成前执行AppBar注销:

protected override void OnClosed(EventArgs e)
{
    // 先清空所有子控件,释放相关托管资源
    Controls.Clear();
    
    // 注销AppBar,确保Shell释放窗口句柄引用
    APPBARDATA msgData = new APPBARDATA();
    msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
    msgData.hWnd = Handle;
    SHAppBarMessage((UInt32)AppBarMessages.Remove, ref msgData);
    
    base.OnClosed(e);
}

// 可移除原OnClosing中的注销代码,避免重复操作
protected override void OnClosing(CancelEventArgs e)
{
    base.OnClosing(e);
}

内容的提问来源于stack exchange,提问作者Brian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 19:09:57