使用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); } }
问题原因
- .NET 6+ WinForms生命周期逻辑变更:与.NET Framework相比,.NET 6+对WinForms的托管线程、消息循环和资源回收机制做了调整。当通过
SHAppBarMessage注册AppBar后,系统Shell会持有窗口句柄的引用;而子控件的存在会改变窗口资源的释放顺序,导致部分未处理的系统消息或托管资源没有被正确回收,进程无法正常终止。 - AppBar注销时机与资源释放不匹配:当前代码在
OnClosing中执行AppBar注销,但此时窗口和子控件的资源尚未开始销毁,Shell可能仍然持有窗口句柄的引用。.NET Framework的句柄回收机制会更快速地释放这类资源,而.NET 6+的优雅回收逻辑会等待所有资源释放完成,进而导致进程挂起。 - 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
相关产品推荐
相关产品推荐

