Task.Delay()触发取消应用程序生存期

时间:2019-07-09 15:07:02

标签: c# .net-core asp.net-core-hosted-services

我遇到了一种情况,其中Task.Delay()方法将触发IApplicationLifetime中的取消事件。这是代码:

    static async Task Main(string[] args)
    {
        Console.WriteLine("Starting");

       await BuildWebHost(args)
            .RunAsync();

        Console.WriteLine("Press any key to exit..");
        Console.ReadKey();
    }

    private static IHost BuildWebHost(string[] args)
    {
        var hostBuilder = new HostBuilder()
            .ConfigureHostConfiguration(config =>
            {
                config.AddEnvironmentVariables();
                config.AddCommandLine(args);
            })
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                configApp.SetBasePath(Directory.GetCurrentDirectory());
                configApp.AddCommandLine(args);
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<BrowserWorkerHostedService>();
                services.AddHostedService<EmailWorkerHostedService>();
            })
            .UseConsoleLifetime();

        return hostBuilder.Build();
    }

以下是正在异常停止的托管服务:

public class BrowserWorkerHostedService : BackgroundService
{
    private IApplicationLifetime _lifetime;
    private IHost _host;

    public BrowserWorkerHostedService(
        IApplicationLifetime lifetime,
        IHost host)
    {
        this._lifetime = lifetime;
        this._host = host;
    }

    protected override async Task ExecuteAsync(CancellationToken stopToken)
    {
        while (!_lifetime.ApplicationStarted.IsCancellationRequested
            && !_lifetime.ApplicationStopping.IsCancellationRequested
            && !stopToken.IsCancellationRequested)
        {
            Console.WriteLine($"{nameof(BrowserWorkerHostedService)} is working. {DateTime.Now.ToString()}");

            //lifetime.StopApplication();
            //await StopAsync(stopToken);

            await Task.Delay(1_000, stopToken);
        }

        Console.WriteLine($"End {nameof(BrowserWorkerHostedService)}");

        await _host.StopAsync(stopToken);
    }
}

public class EmailWorkerHostedService : BackgroundService
{
    private IApplicationLifetime _lifetime;
    private IHost _host = null;

    public EmailWorkerHostedService(
        IApplicationLifetime lifetime,
        IHost host)
    {
        this._lifetime = lifetime;
        this._host = host;
    }

    protected override async Task ExecuteAsync(CancellationToken stopToken)
    {
        while (!_lifetime.ApplicationStarted.IsCancellationRequested
            && !_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)}");

        await _host.StopAsync(stopToken);
    }
}

我希望我的服务能够运行,除非触发了lifetime.StopApplication()。但是,由于lifetime.ApplicationStarted.IsCancellationRequested变量在第二次触发时被设置为true,所以托管服务被停止了。尽管从理论上讲,我没有任何代码明确中止应用程序。

日志将如下所示:

  

正在启动BrowserWorkerHostedService。 09.07.2019 17:03:53

     

EmailWorkerHostedService正在运行。 09.07.2019 17:03:53

     

应用程序已启动。按Ctrl + C关闭。

     

托管环境:生产

     

内容根路径:xxxx

     

结束EmailWorkerHostedService

     

结束BrowserWorkerHostedService

有没有很好的解释为什么Task.Delay()会触发ApplicationStarted取消事件?

1 个答案:

答案 0 :(得分:1)

您正在滥用IApplicationLifetime事件。它们的目的是使您能够与他们关联一些动作。例如,您只想在应用程序完全启动时才开始侦听消息队列。您将要这样做:

_applicationLifetime.ApplicationStarted. Register(StartListenMq);

我认为在这里使用CancellationTokens并不是最好的主意,但是它是实现它的方式。

要取消HostedService时,应仅检查通过ExecuteAsync方法收到的令牌。流程看起来像这样:

IApplicationLifetime.StopApplication() =>将触发IApplicationLifetime.ApplicationStopping =>将触发IHostedService.StopAsync() =>将stopToken

现在问您一个问题:为什么它发生在await Task.Delay()上?再看一下BackgroundService.StartAsync()

    public virtual Task StartAsync(CancellationToken cancellationToken)
    {
        // Store the task we're executing
        _executingTask = ExecuteAsync(_stoppingCts.Token);

        // If the task is completed then return it, this will bubble cancellation and failure to the caller
        if (_executingTask.IsCompleted)
        {
            return _executingTask;
        }

        // Otherwise it's running
        return Task.CompletedTask;
    }

此代码不会等待ExecuteAsync。目前,您在代码StartAsync()中调用异步操作将继续运行。在调用堆栈的某个位置,它将触发ApplicationStarted,并且由于您正在收听而将得到_lifetime.ApplicationStarted.IsCancellationRequested = true