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

为何Task.Delay()会触发IApplicationLifetime的取消事件?

问题原因分析与解决方案

首先,你的问题核心是误解了IApplicationLifetime.ApplicationStarted令牌的设计意图。

ApplicationStarted这个取消令牌的作用是:当应用完全启动完成后,它会被触发取消,用来通知那些等待启动完成的代码“启动已经完成了”——它并不是用来判断应用是否应该继续运行的令牌。所以当你的应用启动完成(也就是控制台输出Application started. Press Ctrl+C to shut down.之后),ApplicationStarted.IsCancellationRequested就会变为true,这直接导致你的循环条件不满足,托管服务就退出了。

而你期望的是服务仅在调用StopApplication()时停止,所以你的循环条件里不应该包含对ApplicationStarted.IsCancellationRequested的判断。

修正后的托管服务代码示例

我们只需要保留stopToken和ApplicationStopping的判断(其实stopToken本身已经包含了应用停止的信号,因为BackgroundService的ExecuteAsync传入的stopToken会在应用停止时被触发),同时注意不要在服务里主动调用_host.StopAsync()——因为当应用收到停止信号时,Host会自动停止所有托管服务,你不需要手动触发:

public class BrowserWorkerHostedService : BackgroundService
{
    private readonly IHostApplicationLifetime _lifetime;

    // 注意:.NET Core 3.0+ 推荐使用 IHostApplicationLifetime 替代过时的 IApplicationLifetime
    public BrowserWorkerHostedService(IHostApplicationLifetime lifetime)
    {
        _lifetime = lifetime;
    }

    protected override async Task ExecuteAsync(CancellationToken stopToken)
    {
        // 只需要监听停止相关的令牌,去掉对 ApplicationStarted 的判断
        while (!_lifetime.ApplicationStopping.IsCancellationRequested && !stopToken.IsCancellationRequested)
        {
            Console.WriteLine($"{nameof(BrowserWorkerHostedService)} is working. {DateTime.Now.ToString()}");
            await Task.Delay(1_000, stopToken);
        }
        Console.WriteLine($"End {nameof(BrowserWorkerHostedService)}");
    }
}

public class EmailWorkerHostedService : BackgroundService
{
    private readonly IHostApplicationLifetime _lifetime;

    public EmailWorkerHostedService(IHostApplicationLifetime lifetime)
    {
        _lifetime = lifetime;
    }

    protected override async Task ExecuteAsync(CancellationToken stopToken)
    {
        while (!_lifetime.ApplicationStopping.IsCancellationRequested && !stopToken.IsCancellationRequested)
        {
            Console.WriteLine($"{nameof(EmailWorkerHostedService)} is working. {DateTime.Now.ToString()}");
            await Task.Delay(1_000, stopToken);
        }
        Console.WriteLine($"End {nameof(EmailWorkerHostedService)}");
    }
}

额外说明

  • 关于IApplicationLifetime的过时问题:在.NET Core 3.0及以后的版本中,IApplicationLifetime已经被标记为过时,官方推荐使用IHostApplicationLifetime,两者的API基本一致,但后者是更现代的替代方案。
  • stopToken的简化使用:BackgroundService传入的stopToken会自动关联到应用的停止信号,所以你甚至可以只判断!stopToken.IsCancellationRequested,代码会更简洁:
    while (!stopToken.IsCancellationRequested)
    {
        Console.WriteLine($"{nameof(BrowserWorkerHostedService)} is working. {DateTime.Now.ToString()}");
        await Task.Delay(1_000, stopToken);
    }
    
  • 避免手动停止Host:你的原代码里在服务退出时调用_host.StopAsync(),这会导致连锁停止,其实当应用收到停止信号(比如Ctrl+C或者调用StopApplication()),Host会自动停止所有托管服务,不需要手动触发。

这样修改后,你的托管服务就会一直运行,直到你调用_lifetime.StopApplication()或者按下Ctrl+C才会停止,符合你的预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:10:49