【半译】扩展shutdown超时设置以保证IHostedService正常关闭
我最近發現一個問題,當應用程序關閉時,我們的應用程序沒有正確執行在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實現。我們將IHostedServices作為每個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 stoppedUnhandled 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 secondsusing (var cts = new CancellationTokenSource(_options.ShutdownTimeout))using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken)){// Create a token, which is cancelled if the timer expiresvar token = linkedCts.Token;// Run StopAsync on each registered hosted serviceforeach (var hostedService in _hostedServices.Reverse()){// stop calling StopAsync if timer expirestoken.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 15sservices.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 optionsservices.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開發實戰視頻課程?★★★
.NET Core實戰項目之CMS 第一章 入門篇-開篇及總體規劃
【.NET Core微服務實戰-統一身份認證】開篇及目錄索引
Redis基本使用及百億數據量中的使用技巧分享(附視頻地址及觀看指南)
.NET Core中的一個接口多種實現的依賴注入與動態選擇看這篇就夠了
10個小技巧助您寫出高性能的ASP.NET Core代碼
用abp vNext快速開發Quartz.NET定時任務管理界面
在ASP.NET Core中創建基于Quartz.NET托管服務輕松實現作業調度
現身說法:實際業務出發分析百億數據量下的多表查詢優化
關于C#異步編程你應該了解的幾點建議
C#異步編程看這篇就夠了
給我好看 您看此文用??·?秒,轉發只需1秒呦~ 好看你就點點我總結
以上是生活随笔為你收集整理的【半译】扩展shutdown超时设置以保证IHostedService正常关闭的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sql Server之旅——第十站 简单
- 下一篇: Asp.Net Core多榜逆袭,这是.