ASP.NET Core 5 Windows服务停止后进程仍运行问题求助
问题场景与现象
我通过IHostBuilder.UseWindowsService()将ASP.NET Core 5 API作为Windows服务运行,Main()方法代码如下:
try { Log.Information("Starting Web Host"); if (_isService) { builder.UseWindowsService(); } var host = builder.Build(); await host.RunAsync(); } catch (Exception e) { Log.Fatal(e, "Host terminated unexpectedly"); } finally { Log.Information("Shutting down, cancelling running actions..."); cancellationTokenSource.Cancel(); await Task.Delay(30000); // 测试用,实际需等待后台任务完成 Log.Information("All tasks shut down, disposing logger."); Log.CloseAndFlush(); cancellationTokenSource.Dispose(); }
停止服务时,预期服务会处于「正在停止」状态,直到finally块所有代码执行完毕才转为「已停止」,但实际服务几乎立即变为「已停止」,进程却持续运行至Task.Delay()结束,后续日志无法输出,重启服务会出现双进程,导致安装程序异常。
问题根源
Windows服务控制管理器(SCM)会在host.RunAsync()执行完成后立即标记服务为「已停止」,此时主机已释放大部分核心资源(包括日志系统的依赖),后续finally块的代码属于进程的残留逻辑,无法被SCM追踪,进而引发进程残留、日志丢失等问题。
解决步骤
1. 利用主机生命周期钩子处理停止逻辑
放弃在Main方法的finally块中处理后台任务等待,改用ASP.NET Core的IHostApplicationLifetime注册停止事件,让SCM正确等待停止流程完成:
try { Log.Information("Starting Web Host"); if (_isService) { builder.UseWindowsService(); } var host = builder.Build(); var appLifetime = host.Services.GetRequiredService<IHostApplicationLifetime>(); var cancellationTokenSource = new CancellationTokenSource(); // 注册服务停止触发的事件 appLifetime.ApplicationStopping.Register(() => { Log.Information("Shutting down, cancelling running actions..."); cancellationTokenSource.Cancel(); // 替换为实际的后台任务等待逻辑,此处用同步等待避免SCM提前结束 Task.Delay(30000).Wait(); Log.Information("All tasks shut down."); }); await host.RunAsync(); } catch (Exception e) { Log.Fatal(e, "Host terminated unexpectedly"); } finally { Log.Information("Disposing logger."); Log.CloseAndFlush(); }
2. 用IHostedService封装后台任务(推荐)
如果存在长期运行的后台任务,将其封装为IHostedService实现,利用StopAsync方法处理停止等待,ASP.NET Core会自动在服务停止时调用该方法并等待执行完成:
public class BackgroundTaskService : IHostedService, IDisposable { private Task _executingTask; private readonly CancellationTokenSource _cts = new CancellationTokenSource(); public Task StartAsync(CancellationToken cancellationToken) { _executingTask = ExecuteAsync(_cts.Token); return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { Log.Information("Stopping background task..."); _cts.Cancel(); // 等待任务完成,设置超时避免无限等待 if (_executingTask != null) { await Task.WhenAny(_executingTask, Task.Delay(30000, cancellationToken)); } Log.Information("Background task stopped."); } private async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // 此处替换为实际后台任务逻辑 await Task.Delay(1000, stoppingToken); } } public void Dispose() { _cts.Cancel(); _cts.Dispose(); } }
在Program中注册该服务:
builder.Services.AddHostedService<BackgroundTaskService>();
3. 调整Windows服务停止超时
通过命令行调整服务的停止超时时间,确保SCM等待足够长的时间让停止逻辑完成:
sc config YourServiceName stoptimeout= 30000
(单位为毫秒,30000即30秒,需替换YourServiceName为实际服务名称)
效果验证
修改后,停止服务时SCM会等待停止逻辑执行完毕,服务会保持「正在停止」状态直到流程结束,进程会正常退出,日志可完整输出,不会出现双进程问题。
内容的提问来源于stack exchange,提问作者woutervanrobays

