You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 13:00:15