.NET7 Worker Service在systemd下ExecuteAsync无法优雅停止
.NET7 Worker Service 作为systemd服务时的优雅关闭问题
问题场景
使用.NET7开发Worker Service并部署为systemd服务,执行systemctl stop my.service时,期望应用执行优雅关闭逻辑,但发现ExecuteAsync中取消令牌触发后的代码从未执行,而StopAsync中的代码可以正常运行。可通过journalctl --unit=my.service -n 50 --no-pager查看运行及停止日志。
现有代码
public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; private readonly IConfiguration _config; public Worker(ILogger<Worker> logger, IConfiguration config) { _logger = logger; _config = config; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("{env} - Worker running at: {time}",_config["env"], DateTimeOffset.Now); await Task.Delay(5000, stoppingToken); } _logger.LogInformation("this line is never executed after stop"); } public override async Task StopAsync(CancellationToken stoppingToken) { await base.StopAsync(stoppingToken); _logger.LogInformation("this line is actually executed after stop"); } }
问题原因
执行systemctl stop时,StopAsync中的base.StopAsync(stoppingToken)会触发stoppingToken的取消信号。此时ExecuteAsync中的Task.Delay(5000, stoppingToken)会立即抛出OperationCanceledException,这个异常直接中断循环,导致循环后的收尾代码无法执行。BackgroundService基类会自动捕获该异常,不会引发程序崩溃,但也跳过了后续的收尾逻辑。
解决方案
方案1:捕获取消异常,确保收尾代码执行
修改ExecuteAsync方法,捕获Task.Delay抛出的取消异常,让循环正常退出并执行后续代码:
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("{env} - Worker running at: {time}",_config["env"], DateTimeOffset.Now); try { await Task.Delay(5000, stoppingToken); } catch (OperationCanceledException) { // 捕获取消信号,退出循环以执行收尾代码 break; } } _logger.LogInformation("this line will execute after stop now"); }
方案2:调整StopAsync逻辑,等待ExecuteAsync完成
在StopAsync中先等待ExecuteAsync的任务完成,再执行自身的收尾逻辑(需设置合理超时,避免无限等待):
public override async Task StopAsync(CancellationToken stoppingToken) { // 等待ExecuteAsync完成收尾,超时时间可根据实际情况调整 await Task.WhenAny(ExecuteAsync(stoppingToken), Task.Delay(TimeSpan.FromSeconds(30), stoppingToken)); await base.StopAsync(stoppingToken); _logger.LogInformation("this line is actually executed after stop"); }
额外配置:确保systemd给足优雅关闭时间
在systemd的服务配置文件(如my.service)中添加超时设置,避免systemd在应用完成收尾前强制杀死进程:
[Service] TimeoutStopSec=30
内容的提问来源于stack exchange,提问作者Ben Guri
相关产品推荐
相关产品推荐

