扩展shutdown超时设置以保证IHostedService正常关闭
source link: http://mp.weixin.qq.com/s?__biz=MzU4Mjc4NzgyOQ%3D%3D&%3Bmid=2247485215&%3Bidx=1&%3Bsn=c6c655dc0063b56c4ce94dae56462913
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
我最近发现一个问题,当应用程序关闭时,我们的应用程序没有正确执行在 IHostedService
中的 StopAsync
方法。经过反复验证发现,这是由于某些服务对关闭信号做出响应所需的时间太长导致的。在这篇文章中,我将展示出现这个问题的一个示例,并且会讨论它为什么会发生以及如何避免这种情况出现。
作者:依乐祝
首发地址:https://www.cnblogs.com/yilezhu/p/12952977.html
英文地址:https://andrewlock.net/extending-the-shutdown-timeout-setting-to-ensure-graceful-ihostedservice-shutdown/
使用IHostedService运行后台服务
ASP.NET Core 2.0引入了 IHostedService
用于运行后台任务的界面。该接口包含两种方法:
public interface IHostedService { Task StartAsync(CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken); }
StartAsync
在应用程序启动时被调用。在ASP.NET核心2.X发生这种情况只是 之后 在应用程序启动处理请求,而在ASP.NET核心3.x中托管服务开始只是 之前 在应用程序启动处理请求。
StopAsync
当应用程序收到shutdown( SIGTERM
)信号时(例如,您CTRL+C在控制台窗口中按入,或者应用程序被主机系统停止时),将调用。这样,您就可以关闭所有打开的连接,处置资源,并通常根据需要清理类。
实际上,实现此接口实际上有一些微妙之处,这意味着您通常希望从helper类 BackgroundService
派生。
如果您想了解更多,Steve Gordon会开设有关Pluralsight的课程“ 构建ASP.NET Core托管服务和.NET Core Worker Services ”。
关闭 IHostedService
实施的问题
我最近看到的问题是 OperationCanceledException
在应用程序关闭时引发的问题:
Unhandled exception. System.OperationCanceledException: The operation was canceled. at System.Threading.CancellationToken.ThrowOperationCanceledException() at Microsoft.Extensions.Hosting.Internal.Host.StopAsync(CancellationToken cancellationToken)
我将这个问题的根源追溯到一个特定的 IHostedService
实现。我们将 IHostedService
s作为每个Kafka消费者的主机。具体操作并不重要-关键在于关闭 IHostedService
相对较慢:取消订阅可能需要几秒钟。
问题的一部分是Kafka库(和基础 librdkafka
库)使用同步阻塞 Consume
调用而不是异步可取消调用的方式。解决这个问题的方法不是很好。
理解此问题的简便方法是一个示例。
演示问题
解决此问题的最简单方法是创建一个包含两个 IHostedService
实现的应用程序:
-
NormalHostedService
在启动和关闭时记录日志,然后立即返回。 -
SlowHostedService
记录启动和停止的时间,但要花10秒才能完成关闭
这两个类的实现如下所示。的 NormalHostedService
很简单:
public class NormalHostedService : IHostedService { readonly ILogger<NormalHostedService> _logger; public NormalHostedService(ILogger<NormalHostedService> logger) { _logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("NormalHostedService started"); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("NormalHostedService stopped"); return Task.CompletedTask; } }
在 SlowHostedService
几乎是相同的,但它有一个 Task.Delay
是需要10秒,以模拟一个缓慢的关机
public class SlowHostedService : IHostedService { readonly ILogger<SlowHostedService> _logger; public SlowHostedService(ILogger<SlowHostedService> logger) { _logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("SlowHostedService started"); return Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("SlowHostedService stopping..."); await Task.Delay(10_000); _logger.LogInformation("SlowHostedService stopped"); } }
的 IHostedService
就是我曾在实践中只用了1秒关机,但我们有很多人,所以整体效果是一样的上面!
该服务中注册的顺序 ConfigureServices
是非常重要的在这种情况下-来证明这个问题,我们需要 SlowHostedService
被关闭 第一 。服务以相反的顺序关闭,这意味着我们需要 最后 注册它:
public void ConfigureServices(IServiceCollection services) { services.AddHostedService<NormalHostedService>(); services.AddHostedService<SlowHostedService>(); }
当我们运行该应用程序时,您将像往常一样看到启动日志:
info: ExampleApp.NormalHostedService[0] NormalHostedService started info: ExampleApp.SlowHostedService[0] SlowHostedService started ... info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down.
但是,如果按CTRL+C关闭该应用程序,则会出现问题。在 SlowHostedService
完成关闭,但随后一个 OperationCanceledException
被抛出:
info: Microsoft.Hosting.Lifetime[0] Application is shutting down... info: ExampleApp.SlowHostedService[0] SlowHostedService stopping... info: ExampleApp.SlowHostedService[0] SlowHostedService stopped Unhandled exception. System.OperationCanceledException: The operation was canceled. at System.Threading.CancellationToken.ThrowOperationCanceledException() at Microsoft.Extensions.Hosting.Internal.Host.StopAsync(CancellationToken cancellationToken) at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.WaitForShutdownAsync(IHost host, CancellationToken token) at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host) at ExampleApp.Program.Main(String[] args) in C:\repos\andrewlock\blog-examples\SlowShutdown\Program.cs:line 16
该 NormalHostedService.StopAsync()
方法从不调用。如果该服务需要进行一些清理,那么您会遇到问题。例如,也许您需要从Consul处优雅地注销该服务,或者取消订阅Kafka主题-现在不会发生。
那么这是怎么回事?超时从哪里来?
原因:HostOptions.ShutDownTimeout
您可以在应用程序关闭时运行的框架 Host
实现中找到有问题的代码。简化的版本如下所示:
internal class Host: IHost, IAsyncDisposable { private readonly HostOptions _options; private IEnumerable<IHostedService> _hostedServices; public async Task StopAsync(CancellationToken cancellationToken = default) { // Create a cancellation token source that fires after ShutdownTimeout seconds using (var cts = new CancellationTokenSource(_options.ShutdownTimeout)) using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken)) { // Create a token, which is cancelled if the timer expires var token = linkedCts.Token; // Run StopAsync on each registered hosted service foreach (var hostedService in _hostedServices.Reverse()) { // stop calling StopAsync if timer expires token.ThrowIfCancellationRequested(); try { await hostedService.StopAsync(token).ConfigureAwait(false); } catch (Exception ex) { exceptions.Add(ex); } } } // .. other stopping code } }
这里的关键点 CancellationTokenSource
是配置为 HostOptions.ShutdownTimeout
之后触发的。默认情况下,这会在5秒后触发。这意味着5秒后将放弃托管服务关闭- IHostedService
必须在此超时内关闭所有托管服务。
public class HostOptions { public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5); }
在 foreach
循环的第一次迭代中, SlowHostedService.Stopasync()
执行,需要10秒钟才能运行。在第二次迭代中,超过了5s超时,因此 token.ThrowIfCancellationRequested();
抛出 OperationConcelledException
。这将退出控制流,并且 NormalHostedService.Stopasync()
永远不会执行。
有一个简单的解决方案-增加 shutdown
超时时间!
解决方法:增加shutdown超时时间
HostOptions
默认情况下未在任何地方显式配置它,因此您需要在 ConfigureSerices
方法中手动对其进行配置。例如,以下配置将超时增加到15s:
public void ConfigureServices(IServiceCollection services) { services.AddHostedService<NormalHostedService>(); services.AddHostedService<SlowShutdownHostedService>(); // Configure the shutdown to 15s services.Configure<HostOptions>( opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(15)); }
或者,您也可以从配置中加载超时时间。例如,如果将以下内容添加到 appsettings.json :
{ "HostOptions": { "ShutdownTimeout": "00:00:15" } // other config }
然后,您可以将 HostOptions
配置部分绑定到 HostOptions
对象:
public class Startup { public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) { Configuration = configuration; } public void ConfigureServices(IServiceCollection services) { services.AddHostedService<NormalHostedService>(); services.AddHostedService<SlowShutdownHostedService>(); // bind the config to host options services.Configure<HostOptions>(Configuration.GetSection("HostOptions")); } }
这会将序列化的 TimeSpan
值绑定 00:00:15
到该 HostOptions
值,并将超时间设置为15s。使用该配置,现在当我们停止应用程序时,所有服务都将正确关闭:
nfo: Microsoft.Hosting.Lifetime[0] Application is shutting down... info: SlowShutdown.SlowShutdownHostedService[0] SlowShutdownHostedService stopping... info: SlowShutdown.SlowShutdownHostedService[0] SlowShutdownHostedService stopped info: SlowShutdown.NormalHostedService[0] NormalHostedService stopped
现在,您的应用程序将等待15秒,以使所有托管服务在退出之前完成关闭!
摘要
在这篇文章中,我讨论了一个最近发现的问题,该问题是当应用程序关闭时,我们的应用程序未在 IHostedService
实现中的 StopAsync
中运行该方法。这是由于某些后台服务对关闭信号做出响应所需的时间太长,并且超过了关闭超时时间。文中我演示了单个服务需要10秒才能关闭服务来重现问题,但实际上,只要所有服务的 总 关闭时间超过默认5秒,就会发生此问题。
该问题的解决方案是 HostOptions.ShutdownTimeout
使用标准ASP.NET Core IOptions<T>
配置系统将配置值扩展为超过5s 。
往期 精彩 回顾
.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
【.NET Core微服务实战-统一身份认证】开篇及目录索引
Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)
.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了
用abp vNext快速开发Quartz.NET定时任务管理界面
给我好看
您看此文用
·
秒,转发只需1秒呦~
好看你就
点点
我
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK