Task.Delay() triggers cancellation of application lifetime

感情迁移 提交于 2019-12-24 19:16:40

问题


I have faced a situation when Task.Delay() method would trigger an event of cancellation in IApplicationLifetime. Here is the code:

    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();
    }

and here are hosted services which are stopping abnormally:

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);
    }
}

I would like my services to be running, unless lifetime.StopApplication() is triggered. However, hosted services are stopped, because lifetime.ApplicationStarted.IsCancellationRequested variable is set to true upon a second itteration.. Even though, in theory, I have no code that explicitly aborts the application.

Log will look like this:

Starting BrowserWorkerHostedService is working. 09.07.2019 17:03:53

EmailWorkerHostedService is working. 09.07.2019 17:03:53

Application started. Press Ctrl+C to shut down.

Hosting environment: Production

Content root path: xxxx

End EmailWorkerHostedService

End BrowserWorkerHostedService

Is there a good explanation why Task.Delay() triggers ApplicationStarted cancellation event?


回答1:


You are misusing IApplicationLifetime events. Their purpose is to give you the ability to associate some action with them. For example you want to start message queue listening only when you application is fully started. You are going to do it like this:

_applicationLifetime.ApplicationStarted. Register(StartListenMq);

I think using CancellationTokens here wasn't the best idea, but it the way it was implemented.

When you want to cancel your HostedService you should check only token received in ExecuteAsync method. The flow will look look this:

IApplicationLifetime.StopApplication() => will trigger IApplicationLifetime.ApplicationStopping => will trigger IHostedService.StopAsync() => will stopToken

And now for your question: why it happens on await Task.Delay()? Look again at 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;
    }

This code does't await ExecuteAsync. At the moment you call async operation in your code StartAsync() will continue to run. Somewhere in it's callstack it will trigger ApplicationStarted and since you are listening to it you will get _lifetime.ApplicationStarted.IsCancellationRequested = true.



来源:https://stackoverflow.com/questions/56955587/task-delay-triggers-cancellation-of-application-lifetime

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!