如何在.NET 8 WebAPI中捕获Windows程序关闭事件及处理服务终止
解决.NET 8自包含Windows WebAPI捕获窗口/工具栏关闭事件的问题
核心问题分析
你遇到的问题本质是:ApplicationStopping仅响应WebHost主动发起的关闭信号(比如Ctrl+C),而Windows窗口关闭/工具栏右键关闭是操作系统向控制台进程发送的控制台控制事件,默认不会触发WebApplication的生命周期事件;ProcessExit触发时CLR已进入终止流程,大部分对象已被回收,无法正常操作;析构函数(终结器)不可靠,因为进程退出时CLR可能跳过终结器执行。
推荐解决方案
方案1:用IHostedService托管你的DLL服务(优先推荐)
.NET的Host系统会统一管理服务生命周期,无论是Ctrl+C还是Windows关闭事件,Host都会触发优雅关闭流程,调用所有IHostedService的StopAsync方法,此时服务对象完全可用。
步骤1:包装你的DLL服务为IHostedService
public class YourDllServiceHost : IHostedService { private readonly YourDllService _targetService; // 通过依赖注入获取你的DLL服务实例 public YourDllServiceHost(YourDllService targetService) { _targetService = targetService; } public Task StartAsync(CancellationToken cancellationToken) { // 启动你的DLL服务(如果需要) _targetService.Initialize(); return Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { // 在这里执行你需要的终止操作,Host会在应用关闭时自动调用 await _targetService.ExecuteCleanupOperationsAsync(); // 如果是同步操作,直接执行即可:_targetService.ExecuteCleanup(); } }
步骤2:在Program.cs中注册服务
var builder = WebApplication.CreateBuilder(args); // 注册你的DLL服务为单例(根据实际生命周期调整) builder.Services.AddSingleton<YourDllService>(); // 注册托管服务,让Host管理生命周期 builder.Services.AddHostedService<YourDllServiceHost>(); // ... 配置WebAPI路由、中间件等 var app = builder.Build(); app.Run();
方案2:注册控制台关闭事件处理程序(补充方案)
如果无法修改服务的注册方式,可以通过P/Invoke直接捕获Windows的控制台关闭事件,在事件触发时主动调用清理逻辑。
实现控制台关闭事件处理
using System.Runtime.InteropServices; public static class ConsoleShutdownHandler { private delegate bool ConsoleCtrlHandler(CtrlTypes ctrlType); private enum CtrlTypes { CTRL_C_EVENT = 0, CTRL_BREAK_EVENT = 1, CTRL_CLOSE_EVENT = 2, // 对应窗口关闭/工具栏关闭操作 CTRL_LOGOFF_EVENT = 5, CTRL_SHUTDOWN_EVENT = 6 } [DllImport("Kernel32.dll")] private static extern bool SetConsoleCtrlHandler(ConsoleCtrlHandler handler, bool add); private static IHost? _host; public static void Register(IHost host) { _host = host; SetConsoleCtrlHandler(OnConsoleClose, true); } private static bool OnConsoleClose(CtrlTypes ctrlType) { if (ctrlType == CtrlTypes.CTRL_CLOSE_EVENT && _host != null) { // 获取你的DLL服务实例并执行清理 var service = _host.Services.GetRequiredService<YourDllService>(); service.ExecuteCleanup(); // 触发Host的优雅关闭(可选,确保WebAPI正常停止) _host.StopAsync().Wait(); } return true; } }
在Program.cs中注册处理程序
var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton<YourDllService>(); var app = builder.Build(); // 注册控制台关闭事件处理 ConsoleShutdownHandler.Register(app.Services.GetRequiredService<IHost>()); // ... 配置WebAPI中间件 app.Run();
关键注意事项
- 不要依赖析构函数:进程退出时CLR不会等待终结器队列执行,析构函数大概率不会触发,所有清理逻辑必须放在显式调用的方法中。
- 优雅关闭超时:可以通过
HostOptions.ShutdownTimeout配置关闭超时时间,确保清理操作有足够时间完成:builder.Services.Configure<HostOptions>(options => { options.ShutdownTimeout = TimeSpan.FromSeconds(10); });
内容的提问来源于stack exchange,提问作者Po-Sen
相关产品推荐
相关产品推荐

