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

.NET 8中Docker容器SIGTERM触发CancellationToken的方法

问题:Docker容器关闭时.NET 8 BackgroundService的CancellationToken未触发

场景与问题

我在C# .NET 8中开发了一个基于<Project Sdk="Microsoft.NET.Sdk.Web">的项目,代码结构如下:

var builder = WebApplication.CreateBuilder(args);

builder
    .RegisterSharedServices()
    .Services
        .AddSingleton<..., ...>();

// 添加Controllers
builder
    .AddCoreApiSupport() // 自定义扩展方法
    . ...();

// 注册后台服务
builder
    .Services
        .AddHostedService<CoreBackgroundService>();

var webApp = builder.Build();

...

webApp.SetupApiSupport();

await webApp.RunAsync();

项目在Linux Docker容器中运行,包含API接口和BackgroundService实现。我期望通过BackgroundService的ExecuteAsync方法中的CancellationToken捕获Docker容器关闭时的SIGTERM信号:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)

但实际情况是stoppingToken从未触发(比如调用Task.Delay(..., stoppingToken)不会抛出异常),Docker Desktop关闭容器时信号完全被忽略。我尝试注册ApplicationStopping事件也没有效果:

appLifetime.ApplicationStopping.Register(() =>
{
    Console.WriteLine("Stopping!");
});

Console.WriteLine从未执行,仿佛ApplicationStopping事件从未触发。

解决方案

要让CancellationToken在Docker容器关闭时正常触发,需要从以下几个方面调整:

1. 确保Docker容器正确发送SIGTERM信号

Docker默认在执行docker stop时会先发送SIGTERM信号,等待10秒后发送SIGKILL强制终止。但如果你的Docker镜像或启动命令有特殊配置,可能会干扰这个流程:

  • 检查Dockerfile中的ENTRYPOINT或CMD,确保是直接启动.NET应用,而不是通过shell脚本间接启动(shell可能会拦截信号)。例如,推荐使用:
    ENTRYPOINT ["dotnet", "YourApp.dll"]
    
    避免使用:
    CMD ["sh", "-c", "dotnet YourApp.dll"]
    

2. 确认.NET主机的生命周期配置

对于Web SDK项目,默认已启用Web主机生命周期,无需切换到RunConsoleAsync或Console Lifetime,Web主机本身支持处理SIGTERM信号。可以显式配置主机的关闭超时时间,确保后台服务有足够时间处理终止逻辑:

  • 通过代码配置:
    builder.WebHost.ConfigureKestrel(options =>
    {
        options.Limits.ShutdownTimeout = TimeSpan.FromSeconds(30);
    });
    
  • 或者通过appsettings.json配置:
    {
      "Host": {
        "ShutdownTimeout": "00:00:30"
      }
    }
    

3. 检查BackgroundService的实现

确保你的CoreBackgroundService正确使用stoppingToken,避免阻塞终止流程:

  • 所有异步操作都要传入stoppingToken,不要忽略它。示例实现:
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // 执行后台任务逻辑
            await DoWorkAsync(stoppingToken);
            // 携带令牌等待下一次执行
            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        }
        // 添加终止后的清理逻辑
        await CleanupAsync();
    }
    
  • 如果存在长时间运行的同步操作,需定期检查stoppingToken.IsCancellationRequested,主动退出循环。

4. 正确使用IHostApplicationLifetime

注册ApplicationStopping事件时,需确保在主机构建完成后获取IHostApplicationLifetime实例:

var webApp = builder.Build();
var appLifetime = webApp.Services.GetRequiredService<IHostApplicationLifetime>();
appLifetime.ApplicationStopping.Register(() =>
{
    Console.WriteLine("Stopping!");
});

不要在builder.Build()之前尝试获取该实例,此时主机尚未构建完成。

5. 验证信号处理是否生效

可以通过以下方式测试:

  • 在容器内执行kill <pid>(替换为.NET应用的进程ID)发送SIGTERM信号,观察应用是否触发终止逻辑。
  • 查看Docker容器日志,确认是否有终止相关的输出。

内容的提问来源于stack exchange,提问作者hyankov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 07:47:37