IHostedService启动按AddHostedService注册顺序串行执行StartAsync,停止则逆序执行StopAsync;BackgroundService需在ExecuteAsync中正确传递和检查stoppingToken,否则超时将导致强制进程终止。
在 IServiceCollection 中调用 AddHostedService 的先后顺序,直接决定了 IHostedService.StartAsync() 的执行顺序。ASP.NET Core 主机会按注册顺序依次 await 所有 StartAsync() 方法,**不会并发启动**。
StartAsync 严格按 AddHostedService 顺序串行执行StartAsync 抛出未捕获异常,后续所有服务的 StartAsync 都不会执行,主机启动失败BackgroundService 是 IHostedService 的抽象基类,它内部维护了一个 CancellationTokenSource,并在 StopAsync 中触发取消信号。但关键点在于:**所有 IHostedService.StopAsync() 按注册顺序的逆序执行**。

BackgroundService 自动处理了循环等待其后台任务结束(通过 ExecuteAsync 返回的 Task),所以你的 StopAsync 通常只需 await base.StopAsync(),无需手动 Cancel主机默认给所有 StopAsync 总共 5 秒超时(可通过 IHostBuilder.UseShutdownTimeout() 修改)。这个超时是全局的,不是每个服务单独计时。
IHostedService 的 StopAsync 累计耗时超过该阈值,主机将直接调用 Environment.Exit(1) 终止进程StopAsync 还在运行,也不会被等待 —— 没有“尽力而为”机制,超时即弃BackgroundService 的 base.StopAsync() 会 await ExecuteAsync 返回的 Task,但如果该 Task 本身没响应取消(比如没检查 cancellationToken.IsCancellationRequested),就会拖垮整个关机流程最典型的崩溃场景是:后台任务死循环且不响应取消,导致 StopAsync 卡住,最终触发主机强制退出。
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// ❌ 错误:没有把 stoppingToken 传给异步操作,且循环内没检查
await DoWork(); // 如果 DoWork 内部不响应 token,这里可能永远不返回
}
catch (Exception ex)
{
_logger.LogError(ex, "Work failed");
}
await Task.Delay(1000, stoppingToken); // ✅ 正确:Delay 显式接受 token,能及时响应取消
}
}
Task.Delay、HttpClient.SendAsync、Channel.Reader.ReadAsync 等)必须传入 stoppingToken
stoppingToken.ThrowIfCancellationRequested()
ExecuteAsync 中 await 一个不接受 token 的第三方异步方法,除非你确认它内部可被中断BackgroundService 共享状态或资源时,启动/停止顺序 + 取消传播必须显式对齐,否则关机过程不可预测。