Task.Delay () вызывает отмену времени жизни приложения - PullRequest
0 голосов
/ 09 июля 2019

Я столкнулся с ситуацией, когда метод 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

End EmailWorkerHostedService

End BrowserWorkerHostedService

Есть хорошее объяснение, почему Task.Delay() вызывает событие отмены ApplicationStarted?

1 Ответ

1 голос
/ 09 июля 2019

Вы неправильно используете 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.

...