Asp. Net Изящное завершение работы ядра 3 генерирует OperationCanceledException - PullRequest
0 голосов
/ 14 февраля 2020

Я пытаюсь аккуратно прекратить ASP. Net Core 3.1 сервис (который будет работать в Kubernetes). Когда Kubernetes останавливает службу, он отправляет в приложение событие SIGTERM, и в этот момент я хочу, чтобы запросы в полете выполнялись (что может занять несколько секунд) перед завершением ... Я думаю, что я могу поймать это в hostedservice ниже, и, следовательно, не останавливаться сразу.

Следующее работает, но с тайм-аутом 5 секунд или больше, я получаю исключение OperationCanceledException. Может ли кто-нибудь пролить свет на то, почему я получаю исключение OperationCanceledException или как пролить свет на альтернативный способ отложить событие SIGTERM, чтобы разрешить постепенное отключение?

    public static int Main(string[] args)
    {
        var logger = NLogBuilder
            .ConfigureNLog("nlog.config")
            .GetCurrentClassLogger();
        try
        {
            CreateHostBuilder(args)
                .ConfigureServices((hostBuilderContext, services) => { services.AddHostedService<LifetimeEventsHostedService>(); })
                .Build()
                .Run();

            return 0;
        }
        catch (Exception e)
        {
            logger.Fatal(e, "Stopping due to exception");

            return -1;
        }
        finally
        {
            LogManager.Shutdown();
        }
    }

Это размещенная служба ...

    internal class LifetimeEventsHostedService : IHostedService
    {
        private readonly Microsoft.Extensions.Logging.ILogger _logger;
        private readonly IHostApplicationLifetime _appLifetime;

        public LifetimeEventsHostedService(
            ILogger<LifetimeEventsHostedService> logger,
            IHostApplicationLifetime appLifetime)
        {
            _logger = logger;
            _appLifetime = appLifetime;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _appLifetime.ApplicationStarted.Register(OnStarted);
            _appLifetime.ApplicationStopping.Register(OnStopping);
            _appLifetime.ApplicationStopped.Register(OnStopped);

            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }

        private void OnStarted()
        {
            _logger.LogInformation("OnStarted has been called.");

            // Perform post-startup activities here
        }

        private void OnStopping()
        {
            _logger.LogInformation("OnStopping has been called.");
            // Perform on-stopping activities here


            // This works, but a timeout of 5 seconds or more subsequently causes an OperationCanceledException
            Thread.Sleep(5000);
        }

        private void OnStopped()
        {
            _logger.LogInformation("OnStopped has been called.");

            // Perform post-stopped activities here
        }
    }

1 Ответ

2 голосов
/ 16 февраля 2020

Я открыт для альтернативных подходов к постепенному завершению работы с ASP. Net Core 3.1, в его нынешнем виде, я использую размещенный сервис.

В приложении. Net Core Я установил ShutdownTimeout на веб-хосте , однако, установив ShutdownTimeout на универсальном c хосте , я могу изящно ждать несколько секунд (больше, чем значение по умолчанию , что составляет 5 секунд) до выключения. Намек на @PmanAce помог мне с этим разобраться.

Таким образом, следующие коды позволяют мне корректно завершить работу. Одно предупреждение, Thread.Sleep в LifetimeEventsHostedService должно быть меньше option.ShutdownTimeout.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostBuilderContext, services) =>
            {
                services.AddHostedService<LifetimeEventsHostedService>();
                services.Configure<HostOptions>(option =>
                {
                    option.ShutdownTimeout = TimeSpan.FromSeconds(30);
                });
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseKestrel();
                webBuilder.UseStartup<Startup>();
            });
}

Следующие LifetimeEventsHostedService

public class LifetimeEventsHostedService : IHostedService
{
    private readonly IHostApplicationLifetime _hostApplicationLifetime;

    public LifetimeEventsHostedService(IHostApplicationLifetime hostApplicationLifetime)
    {
        _hostApplicationLifetime = hostApplicationLifetime;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _hostApplicationLifetime.ApplicationStarted.Register(OnStarted);
        _hostApplicationLifetime.ApplicationStopping.Register(OnStopping);
        _hostApplicationLifetime.ApplicationStopped.Register(OnStopped);

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }

    private void OnStopped()
    {
        Console.WriteLine("OnStopped");
    }

    private void OnStopping()
    {
        Console.WriteLine("OnStopping");
        Console.WriteLine(DateTime.Now.ToLongTimeString());

        Thread.Sleep(15000);
        Console.WriteLine("Sleep finished");
        Console.WriteLine(DateTime.Now.ToLongTimeString());
    }

    private void OnStarted()
    {
        Console.WriteLine("OnStarted");
    }
}
...