ASP.NET Core应用作为Linux服务无法正常停止的问题求助
问题描述
在Linux Ubuntu机器上创建了一个简单的ASP.NET Core Web应用并作为systemd服务运行:
systemctl start webapp.service能正常启动应用- 执行
systemctl stop webapp.service时会长时间卡住,最终超时 - 查看停止时的stdout,程序卡在
await this.app.StopAsync()语句,未输出预期的"Stopped"日志
停止服务时的输出
Nov 21 08:59:57 LinxServer systemd[1]: Stopping WebApp... Nov 21 08:59:57 LinxServer dotnet[6746]: info: Microsoft.Hosting.Lifetime[0] Nov 21 08:59:57 LinxServer dotnet[6746]: Application is shutting down...
直接运行时的正常关闭输出
用dotnet WebApplication1.dll直接启动,按下Ctrl+C可正常关闭:
info: Microsoft.Hosting.Lifetime[0] Application is shutting down... Stopping... Stopped.
应用代码
await Host .CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { services.AddHostedService<Application>(); }) .UseSystemd() .Build() .RunAsync(); class Application : IHostedService { private WebApplication app; public async Task StartAsync(CancellationToken cancellationToken) { Console.WriteLine("Starting..."); var builder = WebApplication.CreateBuilder(); builder.WebHost.UseKestrel(options => options.ListenAnyIP(8010)); this.app = builder.Build(); this.app.MapGet("/", () => "Hello World!"); await this.app.StartAsync(); Console.WriteLine("Started."); } public async Task StopAsync(CancellationToken cancellationToken) { Console.WriteLine("Stopping..."); await this.app.StopAsync(); Console.WriteLine("Stopped."); } }
Systemd服务单元配置
[Unit] Description=WebApp [Service] Type=notify ExecStart=dotnet WebApplication1.dll WorkingDirectory=/home/myuser/webapp KillSignal=SIGINT User=myuser Environment=ASPNETCORE_ENVIRONMENT=Production [Install] WantedBy=multi-user.target
已尝试的无效修改
改用同步启动方式,问题依旧:
using var host = Host .CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { services.AddHostedService<Application>(); }) .UseSystemd() .Build(); host.Start(); host.WaitForShutdown();
解决思路
1. 修复嵌套主机的生命周期冲突
你在IHostedService内部手动创建并启动了独立的WebApplication实例,导致两个主机生命周期管理流程冲突:外部Host发起停止信号时,内部的WebApplication无法正确接收取消信号,最终卡住StopAsync。
修复代码:直接将Web服务集成到主Host中,不要嵌套创建:
var builder = WebApplication.CreateBuilder(args); // 将systemd集成直接绑定到WebHost builder.WebHost.UseKestrel(options => options.ListenAnyIP(8010)) .UseSystemd(); builder.Services.AddHostedService<Application>(); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); await app.RunAsync(); class Application : IHostedService { public async Task StartAsync(CancellationToken cancellationToken) { Console.WriteLine("Starting..."); // 无需手动启动WebApplication,app.RunAsync会统一管理 Console.WriteLine("Started."); } public async Task StopAsync(CancellationToken cancellationToken) { Console.WriteLine("Stopping..."); // 无需手动调用StopAsync,主Host会处理Web服务的停止 await Task.CompletedTask; Console.WriteLine("Stopped."); } }
2. 为StopAsync添加取消令牌支持
当前StopAsync没有使用传入的cancellationToken,如果app.StopAsync()卡住,无法响应外部的超时取消信号,修改后:
public async Task StopAsync(CancellationToken cancellationToken) { Console.WriteLine("Stopping..."); // 将取消令牌传入StopAsync,确保能响应终止信号 await this.app.StopAsync(cancellationToken); Console.WriteLine("Stopped."); }
3. 调整Systemd服务配置
- 将
Type=notify改为Type=simple:嵌套Host场景下,notify信号传递容易出现异常 - 添加
TimeoutStopSec=10:明确停止超时时间,避免无限等待 - 设置
KillMode=mixed:让systemd先发送终止信号,超时后强制杀死进程
修改后的配置:
[Service] Type=simple ExecStart=dotnet WebApplication1.dll WorkingDirectory=/home/myuser/webapp KillSignal=SIGINT TimeoutStopSec=10 KillMode=mixed User=myuser Environment=ASPNETCORE_ENVIRONMENT=Production
4. 配置Kestrel的关闭超时
如果存在未关闭的长连接,Kestrel会等待连接超时才停止,可手动设置关闭超时:
builder.WebHost.UseKestrel(options => { options.ListenAnyIP(8010); // 设置Kestrel关闭时的等待超时 options.Limits.ShutdownTimeout = TimeSpan.FromSeconds(5); });
内容的提问来源于stack exchange,提问作者bgh
相关产品推荐
相关产品推荐

