【半译】扩展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秒呦~
好看你就点点我

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309496.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[JavaWeb-MySQL]多表关系介绍

多表之间的关系 1. 分类&#xff1a;1. 一对一(了解)&#xff1a;* 如&#xff1a;人和身份证* 分析&#xff1a;一个人只有一个身份证&#xff0c;一个身份证只能对应一个人2. 一对多(多对一)&#xff1a;* 如&#xff1a;部门和员工* 分析&#xff1a;一个部门有多个员工&am…

Asp.Net Core多榜逆袭,这是.NET最好的时代!

摒弃侥幸之念&#xff0c;必取百炼成钢。厚积分秒之功&#xff0c;始得一鸣惊人&#xff01;经过多年的沉沦&#xff0c;.NET终于迎来逆袭&#xff01;近期连出多个排行榜&#xff0c;Asp.Net Core直接霸榜&#xff0c;这意味着属于.Neter的好时代的即将到来&#xff01;.Net C…

[JavaWeb-MySQL]数据库的备份和还原

数据库的备份和还原 1. 命令行&#xff1a;* 语法&#xff1a;* 备份&#xff1a; mysqldump -u用户名 -p密码 数据库名称 > 保存的路径* 还原&#xff1a;1. 登录数据库2. 创建数据库3. 使用数据库4. 执行文件。source 文件路径 2. 图形化工具&#xff1a;备份完成!!! 现…

全局变量初始化顺序探究

缘起 我在上一篇文章——《调试实战 —— dll 加载失败之全局变量初始化篇》中&#xff0c;跟大家分享了一个由于全局变量初始化顺序导致的 dll 加载失败的例子。感兴趣的小伙伴儿可以点击阅读。虽然我们知道了是由于全局变量初始化顺序导致的问题&#xff0c;也给出了解决方案…

java基础知识——面向对象基本概念

文章目录Java基本概念源文件声明规则Java包Import语句继承类型继承的特性继承关键字super 与 this 关键字构造器方法的重写规则重载(Overload)重写与重载之间的区别java 接口接口与类相似点&#xff1a;接口与类的区别&#xff1a;接口特性抽象类和接口的区别接口的声明接口的实…

基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(三)

上一篇完成了全网各大平台的热点新闻数据的抓取&#xff0c;本篇继续围绕抓取完成后的操作做一个提醒。当每次抓取完数据后&#xff0c;自动发送邮件进行提醒。在开始正题之前还是先玩一玩之前的说到却没有用到的一个库PuppeteerSharp。PuppeteerSharp&#xff1a;Headless Chr…

创建型模式——工厂模式

一、 实验目的与要求 1.练习使用工厂模式。设计相关的模拟场景并进行实施&#xff0c;验证模式特性&#xff0c;掌握其优缺点。 2.实验结束后&#xff0c;对相关内容进行总结。 二、实验内容 1.模式应用场景说明 作为一个青年人&#xff0c;最好的伙伴就是手机。而手机最重…

dotNET Core 3.X 依赖注入

如果说在之前的 dotNET 版本中&#xff0c;依赖注入还是个比较新鲜的东西&#xff0c;那么在 dotNET Core 中已经是随处可见了&#xff0c;可以说整个 dotNET Core 的框架是构建在依赖注入框架之上。本文说说对 dotNET Core 中依赖注入的理解。什么是依赖在面向对象的语言中&am…

创建型模式——抽象工厂模式

一、 实验目的与要求 1.练习使用工厂模式。设计相关的模拟场景并进行实施&#xff0c;验证模式特性&#xff0c;掌握其优缺点。 2.实验结束后&#xff0c;对相关内容进行总结。 二、实验内容 1.模式应用场景说明 手机CPU生产工厂&#xff1a;在一个工厂里面&#xff0c;有A…

[JavaWeb-MySQL]多表查询概述

多表查询&#xff1a; * 查询语法&#xff1a;select列名列表from表名列表where.... * 准备sql# 创建部门表CREATE TABLE dept(id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20));INSERT INTO dept (NAME) VALUES (开发部),(市场部),(财务部);# 创建员工表CREATE TABLE em…

【壹刊】Azure AD(三)Azure资源的托管标识

一&#xff0c;引言来个惯例&#xff0c;吹水&#xff01;????????????????????前一周因为考试&#xff0c;还有个人的私事&#xff0c;一下子差点颓废了。想了想&#xff0c;写博客这种的东西还是得坚持&#xff0c;再忙&#xff0c;也要检查。要养成一种…

[JavaWeb-JDBC]JDBC概念

JDBC&#xff1a; 1. 概念&#xff1a;Java DataBase Connectivity Java 数据库连接&#xff0c; Java语言操作数据库JDBC本质&#xff1a;其实是官方&#xff08;sun公司&#xff09;定义的一套操作所有关系型数据库的规则&#xff0c;即接口。各个数据库厂商去实现这套接口…

创建型模式——建造者模式

一、 实验目的与要求 1.练习使用工厂模式。设计相关的模拟场景并进行实施&#xff0c;验证模式特性&#xff0c;掌握其优缺点。 2.实验结束后&#xff0c;对相关内容进行总结。 二、实验内容 1.模式应用场景说明 Decis创建一个获取多套餐信息&#xff0c;包含A套餐&#xf…

android studio模拟器的安装与使用

来回弄了好几遍&#xff0c;网上也都搜过下载过很多版本&#xff0c;其中夜神模拟器是真的方便&#xff0c;也好用&#xff0c;棒极了&#xff01;那么我就来分享一下&#xff1a; 第一&#xff0c;肯定是下载啦 下载链接&#xff1a;夜神模拟器官方 接着就是连接了&#xff0c…

如何训练解决问题的能力?

作为程序员&#xff0c;技术能力固然很重要&#xff0c;但平时除了提升技术能力也别忽略了其它方面的能力。你可以写一辈子代码&#xff0c;但你不能一辈子只写代码。当你的技术能力足以使你在公司站稳脚跟时&#xff0c;你可以停下来锻炼自己的管理能力&#xff0c;比如职场中…

PS照片换底色

因为写简历嘛&#xff0c;手边没有白色底的照片&#xff0c;就用ps换了个底色&#xff0c;记录一下&#xff0c;下次可能还要用。这里我用幂幂的照片来代替。 打开ps&#xff0c;点击文件&#xff0c;点击打开&#xff0c;找到你需要处理的照片。 -点击旁边的对象选择工具&…

小心 HttpClient 中的 FormUrlEncodeContent 的 bug

小心 HttpClient 中的 FormUrlEncodeContent 的 bugIntro最近发现活动室预约项目里的上传图片有时候会有问题&#xff0c;周末找时间测试了一下&#xff0c;发现小图片的上传没问题&#xff0c;大图片上传会有问题&#xff0c;而且异常信息还很奇怪&#xff0c;System.UriForma…

IDEA导入MySQL的jdbc驱动出现“java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver”

目录 一、一般的解决思路 1、JDBC下载链接 2、选择下载内容&#xff0c;并进行下载 3、将驱动导入java项目 二、依然导入驱动失败怎么办 当我们在idea中使用java操作mysql数据库时会出现&#xff1a; Exception in thread "main" java.lang.ClassNotFoundExce…

Android程序设计基础-设计布局之伪今日头条主界面

一、 实验目的 &#xff08;1&#xff09; 掌握Andriod Studio的基本使用方法&#xff1b; &#xff08;2&#xff09; 掌握Andriod Studio中常用的控件及其使用方法&#xff1b; 二、实验内容 &#xff08;1&#xff09;使用Android Studio编写任意一个Android程序并运行&a…

基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…