Asp.Net Core 3 优雅关闭抛出 OperationCanceledException

Posted

技术标签:

【中文标题】Asp.Net Core 3 优雅关闭抛出 OperationCanceledException【英文标题】:Asp.Net Core 3 graceful shutdown throws OperationCanceledException 【发布时间】:2020-05-30 09:21:17 【问题描述】:

我正在尝试优雅地终止 ASP.Net Core 3.1 服务(它将在 Kubernetes 中运行)。当 Kubernetes 停止服务时,它会向应用程序发送一个 SIGTERM 事件,此时我希望在终止之前完成飞行中的请求(可能需要几秒钟)......我想我可以在托管服务中捕捉到这一点,如下方,因此不会立即停止。

以下工作,但超时 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
        
    

【问题讨论】:

取消标记是处理操作取消异常的标记。可能如果您正在做的事情比关机时间长,取消令牌将抛出该异常。 SIGTERM 默认超时为 5 秒,因此如果您尝试再等待,繁荣,操作超时异常。 那你到底要怎么改变呢? 不熟悉如何更改它 kubernetes 但我不会在那里更改它。例如,您可以尝试实现一个队列来存储作业以防关机。 @Neil - 感谢您的意见,这个问题专门针对 Kubernetes 中的正常关闭场景,您希望停止接收新请求(新请求将被定向到应用程序的另一个实例)和然后在飞行请求完成后关闭。来自go“允许服务器通过仅在服务于所有正在运行的请求后关闭来最大限度地减少停机时间” - 我正在尝试在这里实现同样的目标! 【参考方案1】:

我对使用 ASP.Net Core 3.1 进行正常关闭的替代方法持开放态度,就目前而言,我正在使用托管服务。

在 .Net Core 应用程序中,我设置了 ShutdownTimeout on the webhost,但是,设置 ShutdownTimeout on the generic host 确实允许我在关机前优雅地等待几秒钟(超过默认的 sigterm,即 5 秒) . @PmanAce 的提示帮助我解决了这个问题。

因此,以下代码允许我优雅地终止。需要注意的是,LifetimeEventsHostedService 中的 Thread.Sleep 必须小于 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");
    

【讨论】:

以上是关于Asp.Net Core 3 优雅关闭抛出 OperationCanceledException的主要内容,如果未能解决你的问题,请参考以下文章

优雅地停止ASP.NET Core Web App(从Visual Studio调试器中),这是一个与IHostedService相关的问题

asp.net core 中优雅的进行响应包装

PUT 请求抛出 403 CORS 错误 Asp.Net Core 3.1 + Vue3 +IIS10

如何实现 asp.net core 安全优雅退出 ?

部署到 Ubuntu 18.04 的 ASP.NET Core 3.0 应用程序抛出无法加载文件或程序集 System.Diagnostics.TraceSource

ASP.NET Core gRPC 集成 Polly 实现优雅重试