.NET Worker Service 如何优雅退出

上一篇文章中我们了解了 .NET Worker Service 的入门知识[1],今天我们接着介绍一下如何优雅地关闭和退出 Worker Service。

Worker 类

从上一篇文章中,我们已经知道了 Worker Service 模板为我们提供三个开箱即用的核心文件,其中 Worker 类是继承自抽象基类 BackgroundService 的,而 BackgroundService 实现了 IHostedService 接口。最终 Worker 类会被注册为托管服务,我们处理任务的核心代码就是写在 Worker 类中的。所以,我们需要重点了解一下 Worker 及其基类。

先来看看它的基类 BackgroundService :

基类 BackgroundService 中有三个可重写的方法,可以让我们绑定到应用程序的生命周期中:

  • 抽象方法 ExecuteAsync:作为应用程序主要入口点的方法。如果此方法退出,则应用程序将关闭。我们必须在 Worker 中实现它。

  • 虚方法 StartAsync:在应用程序启动时调用。如果需要,可以重写此方法,它可用于在服务启动时一次性地设置资源;当然,也可以忽略它。

  • 虚方法 StopAsync:在应用程序关闭时调用。如果需要,可以重写此方法,在关闭时释放资源和销毁对象;当然,也可以忽略它。

默认情况下 Worker 只重写必要的抽象方法 ExecuteAsync

新建一个 Worker Service 项目

我们来新建一个 Worker Service,使用 Task.Delay 来模拟关闭前必须完成的一些操作,看看是否可以通过简单地在 ExecuteAsync 中 Delay 来模拟实现优雅关闭。

需要用到的开发工具:

  • Visual Studio Code:https://code.visualstudio.com/

  • 最新的 .NET SDK:https://dotnet.microsoft.com/download

安装好以上工具后,在终端中运行以下命令,创建一个 Worker Service 项目:

dotnet new Worker -n "MyService"

创建好 Worker Service 后,在 Visual Studio Code 中打开应用程序,然后构建并运行一下,以确保一切正常:

dotnet build
dotnet run

按 CTRL+C 键关闭服务,服务会立即退出,默认情况下 Worker Service 的关闭就是这么直接!在很多场景(比如内存中的队列)中,这不是我们想要的结果,有时我们不得不在服务关闭前完成一些必要的资源回收或事务处理

我们看一下 Worker 类的代码,会看到它只重写了基类 BackgroundService 中的抽象方法 ExecuteAsync

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{while (!stoppingToken.IsCancellationRequested){_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);await Task.Delay(1000, stoppingToken);}
}

我们尝试修改一下此方法,退出前做一些业务处理:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{while (!stoppingToken.IsCancellationRequested){_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);// await Task.Delay(1000, stoppingToken);await Task.Delay(1000);}_logger.LogInformation("等待退出 {time}", DateTimeOffset.Now);Task.Delay(60_000).Wait(); //模拟退出前需要完成的工作_logger.LogInformation("退出 {time}", DateTimeOffset.Now);
}

然后测试一下,看它是不是会像我们预期的那样先等待 60 秒再关闭。

dotnet build
dotnet run

按 CTRL+C 键关闭服务,我们会发现,它在输出 “等待退出” 后,并没有等待 60 秒并输出 “退出” 之后再关闭,而是很快便退出了。这就像我们熟悉的控制台应用程序,默认情况下,在我们点了右上角的关闭按钮或者按下 CTRL+C 键时,会直接关闭一样。

Worker Service 优雅退出

那么,怎么才能实现优雅退出呢?

方法其实很简单,那就是将 IHostApplicationLifetime 注入到我们的服务中,然后在应用程序停止时手动调用 IHostApplicationLifetime 的 StopApplication 方法来关闭应用程序。

修改 Worker 的构造函数,注入 IHostApplicationLifetime

private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly ILogger<Worker> _logger;public Worker(IHostApplicationLifetime hostApplicationLifetime, ILogger<Worker> logger)
{_hostApplicationLifetime = hostApplicationLifetime;_logger = logger;
}

然后在 ExecuteAsync 中,处理完退出前必须完成的业务逻辑后,手动调用 IHostApplicationLifetime 的 StopApplication 方法,下面是丰富过的 ExecuteAsync 代码:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{try{// 这里实现实际的业务逻辑while (!stoppingToken.IsCancellationRequested){try{_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);await SomeMethodThatDoesTheWork(stoppingToken);}catch (Exception ex){_logger.LogError(ex, "Global exception occurred. Will resume in a moment.");}await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);}}finally{_logger.LogWarning("Exiting application...");GetOffWork(stoppingToken); //关闭前需要完成的工作_hostApplicationLifetime.StopApplication(); //手动调用 StopApplication}
}private async Task SomeMethodThatDoesTheWork(CancellationToken cancellationToken)
{_logger.LogInformation("我爱工作,埋头苦干ing……");await Task.CompletedTask;
}/// <summary>
/// 关闭前需要完成的工作
/// </summary>
private void GetOffWork(CancellationToken cancellationToken)
{_logger.LogInformation("啊,糟糕,有一个紧急 bug 需要下班前完成!!!");_logger.LogInformation("啊啊啊,我爱加班,我要再干 20 秒,Wait 1 ");Task.Delay(TimeSpan.FromSeconds(20)).Wait();_logger.LogInformation("啊啊啊啊啊啊,我爱加班,我要再干 1 分钟,Wait 2 ");Task.Delay(TimeSpan.FromMinutes(1)).Wait();_logger.LogInformation("啊哈哈哈哈哈,终于好了,下班走人!");
}

此时,再次 dotnet run 运行服务,然后按 CTRL+C 键关闭服务,您会发现关闭前需要完成的工作 GetOffWork 运行完成后才会退出服务了。

至此,我们已经实现了 Worker Service 的优雅退出。

StartAsync 和 StopAsync

为了更进一步了解 Worker Service,我们再来丰富一下我们的代码,重写基类 BackgroundService 的 StartAsync 和 StopAsync 方法:

public class Worker : BackgroundService
{private bool _isStopping = false; //是否正在停止工作private readonly IHostApplicationLifetime _hostApplicationLifetime;private readonly ILogger<Worker> _logger;public Worker(IHostApplicationLifetime hostApplicationLifetime, ILogger<Worker> logger){_hostApplicationLifetime = hostApplicationLifetime;_logger = logger;}public override Task StartAsync(CancellationToken cancellationToken){_logger.LogInformation("上班了,又是精神抖擞的一天,output from StartAsync");return base.StartAsync(cancellationToken);}protected override async Task ExecuteAsync(CancellationToken stoppingToken){try{// 这里实现实际的业务逻辑while (!stoppingToken.IsCancellationRequested){try{_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);await SomeMethodThatDoesTheWork(stoppingToken);}catch (Exception ex){_logger.LogError(ex, "Global exception occurred. Will resume in a moment.");}await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);}}finally{_logger.LogWarning("Exiting application...");GetOffWork(stoppingToken); //关闭前需要完成的工作_hostApplicationLifetime.StopApplication(); //手动调用 StopApplication}}private async Task SomeMethodThatDoesTheWork(CancellationToken cancellationToken){if (_isStopping)_logger.LogInformation("假装还在埋头苦干ing…… 其实我去洗杯子了");else_logger.LogInformation("我爱工作,埋头苦干ing……");await Task.CompletedTask;}/// <summary>/// 关闭前需要完成的工作/// </summary>private void GetOffWork(CancellationToken cancellationToken){_logger.LogInformation("啊,糟糕,有一个紧急 bug 需要下班前完成!!!");_logger.LogInformation("啊啊啊,我爱加班,我要再干 20 秒,Wait 1 ");Task.Delay(TimeSpan.FromSeconds(20)).Wait();_logger.LogInformation("啊啊啊啊啊啊,我爱加班,我要再干 1 分钟,Wait 2 ");Task.Delay(TimeSpan.FromMinutes(1)).Wait();_logger.LogInformation("啊哈哈哈哈哈,终于好了,下班走人!");}public override Task StopAsync(CancellationToken cancellationToken){_logger.LogInformation("太好了,下班时间到了,output from StopAsync at: {time}", DateTimeOffset.Now);_isStopping = true;_logger.LogInformation("去洗洗茶杯先……", DateTimeOffset.Now);Task.Delay(30_000).Wait();_logger.LogInformation("茶杯洗好了。", DateTimeOffset.Now);_logger.LogInformation("下班喽 ^_^", DateTimeOffset.Now);return base.StopAsync(cancellationToken);}
}

重新运行一下

dotnet build
dotnet run

然后按 CTRL+C 键关闭服务,看看运行结果是什么?

我们可以观察到在 Worker Service 启动和关闭时,基类 BackgroundService 中可重写的三个方法的运行顺序分别如下图所示:

总结

在本文中,我通过一个实例介绍了如何优雅退出 Worker Service 的相关知识。

Worker Service 本质上仍是一个控制台应用程序,执行一个作业。但它不仅可以作为控制台应用程序直接运行,也可以使用 sc.exe 实用工具安装为 Windows 服务,还可以部署到 linux 机器上作为后台进程运行。以后有时间我会介绍更多关于 Worker Service 的知识。

您可以从 GitHub 下载本文中的源码[2]


相关链接:

  1. https://mp.weixin.qq.com/s/ujGkb5oaXq3lqX_g_eQ3_g .NET Worker Service 入门介绍 ↩︎

  2. https://github.com/ITTranslate/WorkerServiceGracefullyShutdown 源码下载 ↩︎

作者 :技术译民
出品 :技术译站(https://ITTranslator.cn/)

END

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

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

相关文章

NYOJ -123 士兵杀敌(四)

士兵杀敌&#xff08;四&#xff09; 时间限制&#xff1a;2000 ms | 内存限制&#xff1a;65535 KB难度&#xff1a;5描述南将军麾下有百万精兵&#xff0c;现已知共有M个士兵&#xff0c;编号为1~M&#xff0c;每次有任务的时候&#xff0c;总会有一批编号连在一起人请战&a…

大数据告诉你,中国女人有多勤奋

全世界只有3.14 % 的人关注了数据与算法之美前段时间&#xff0c;美国国家统计局发布了一组关于世界各国劳动参与率的数据&#xff0c;中国赫然位列世界第一&#xff0c;劳动总量世界第一&#xff0c;劳动参与率世界第一。所谓劳动总量&#xff0c;就是所有工作的人的工作时间的…

get+php+mysql_Apache+PHP+MySql 的安装及配置

每一项技术用的人多了&#xff0c;就会有人将其进行优化&#xff0c;做成一个简单、实用、大众化的工具&#xff0c;这对于初识者来说是非常方便的&#xff0c;但是对于长久学习或工作这方面的人技术人员来说是不可取的&#xff0c;所以还是要学习基础的实用方法。因此&#xf…

记一次 .NET 车联网云端服务 CPU爆高分析

一&#xff1a;背景 1. 讲故事前几天有位朋友wx求助&#xff0c;它的程序CPU经常飙满&#xff0c;没找到原因&#xff0c;希望帮忙看一下。这些天连续接到几个cpu爆高的dump&#xff0c;都看烦了????????????&#xff0c;希望后面再来几个其他方面的dump&#xff0…

下载 infoq 网站视频

今天看到 infoq 网站上一个讲 Go 语言的视频&#xff0c;速度太卡了。我家里光纤宽带也没法正常浏览&#xff0c;所以需要研究下如何下载了。 用 FireBug 看了下源代码&#xff0c;抓到其中 flash 播放控件的一个参数里有视频链接如下&#xff1a; <param name"flashva…

java swing 示例_JAVA简单Swing图形界面应用演示样例

JAVA简单Swing图形界面应用演示样例package org.rui.hello;import javax.swing.JFrame;/*** 简单的swing窗体* author lenovo**/public class HelloSwing {public static void main(String[] args) {JFrame framenew JFrame("hello Swing");frame.setDefaultCloseOpe…

group client policy无法登录,谢绝访问

以下是联想提供的临时解决方案&#xff1a; 1、在用户开机时&#xff0c;不停点击F8按键&#xff0c;在弹出的”高级启动选项”中使用键盘上的方向箭头中的向下箭头移动白色高亮条&#xff0c;选择”安全模式”&#xff0c;敲击回车键。   2、进入安全模式界面时&#xff0c;…

.NET上海社区线下Meetup - 5.22 Blazor Day

Blazor 是一个 Web UI 框架&#xff0c;Blazor 旨在简化快速的单页面 .Net 浏览器应用的构建过程&#xff0c;它虽然使用了诸如 CSS 和 HTML 之类的 Web 技术&#xff0c;但它使用 C&#xff03;语言和 Razor 语法代替 JavaScript 来构建可组合的 Web UI 。通过提供用于编译到 …

入门机器学习,开启人工智能大门!

AI这个词相信大家都非常熟悉&#xff0c;近几年来人工智能圈子格外热闹&#xff0c;光是AlphoGo就让大家对它刮目相看。今天小天就来跟大家唠一唠如何进军人工智能的第一步——机器学习。在机器学习领域&#xff0c;Python已经成为了主流。一方面因为这门语言简单易上手&#x…

java集合框架的结构_集合框架(Collections Framework)详解及代码示例

简介集合和数组的区别&#xff1a;数组存储基础数据类型&#xff0c;且每一个数组都只能存储一种数据类型的数据&#xff0c;空间不可变。集合存储对象&#xff0c;一个集合中可以存储多种类型的对象。空间可变。严格地说&#xff0c;集合是存储对象的引用&#xff0c;每个对象…

oracle undo

UNDO表空间用于存放UNDO数据,当执行DML操作(INSERT,UPDATE和DELETE)时,oracle会将这些操作执行前的旧数据 写入到 UNDO段,在oracle9i之前,管理UNDO数据时使用(Rollback Segment)完成的.从oracle9i开始,管理UNDO数据不仅可以使用回滚段,还可以使用UNDO表空间.因为规划和管理回滚…

Unity3D OpenVR 虚拟现实 保龄球打砖块游戏开发

据说水哥买了 Valve Index 设备&#xff0c;既然这个设备这么贵&#xff0c;不开发点有&#xff08;zhi&#xff09;趣&#xff08;zhang&#xff09;游戏就感觉对不起这个设备。本文将来开始着手开发一个可玩性不大&#xff0c;观赏性极强的保龄球打砖块游戏。这仅仅只是一个入…

mac mysql 移动硬盘_MAC一些高能过程记录(一些没必要的坑)

搞计算机的&#xff0c;谁电脑上没个数据库&#xff0c;不管用不用的着&#xff0c;有时候总需要&#xff0c;比如调试下博客呀之类的, 毕竟一般都会觉得数据库很好玩啊1.MySql安装&#xff1a;dmg、pkg什么的直接装吧&#xff0c;结束后会给你一个提示&#xff0c;上面会有密…

数学思维比数学运算更重要

全世界只有3.14 % 的人关注了数据与算法之美数学的证明依靠严密的逻辑推理&#xff0c;一经证明就永远正确&#xff0c;所以&#xff0c;数学证明是绝对的。相对而言&#xff0c;科学的证明则依赖于观察、实验数据和理解力&#xff0c;科学理论的证明难以达到数学定理证明所具有…

多年前那些优秀的工程师,后来都去哪儿了?

这是头哥侃码的第241篇原创上周末&#xff0c;我读初中的儿子突然问我&#xff1a;“爸爸&#xff0c;你是不是从好买离职了&#xff1f;”我听完&#xff0c;忙惊讶地问他是怎么知道的。他朝我做了个鬼脸&#xff0c;然后指了指我的手机说&#xff1a;“你的文章写的如此生动&…

使用easyUI 格式化datagrid列

author YHC 以下示例格式化在easyui DataGrid 里的列数据,和使用自定义列formatter ,如果价格小于20就将文本变为红色. 查看 Demo 格式化一个DataGrid 列,我们需要设置formatter 属性它是一个函数,这个格式化函数包含三个参数: value: 当前列对应字段值.row: 当前的row(行)记录…

makefile obj文件路径_Makefile一问:如何修改.o输出文件的输出路径 Linux/Unix社区 / 程序开......

我在Linux下编成时&#xff0c;用Makefile文件生成的.o文件老是跟我的源文件在同一个目录下&#xff0c;搞得我用TAB键之余还得先再敲.c再TAB&#xff0c;为什么要多敲两次键盘&#xff1f;如果我把所有生成的.o文件放比如./obj目录下&#xff0c;岂不是少来很多麻烦&#xff1…

PreparedStatement

1 //一般使用方法2 //优点在于简化sql语句的使用3 4 5 int deptno 0;6 String dname args[1];7 String loc args[2];8 9 10 11 Connection conn null; 12 13 PreparedStatement stmt null; 14 15 Class.forName("oracle.jdbc.driver.OracleDriver"); 16 17…

让Dapper支持Mock

Dapper&#xff0c;在.net的ORM中占有一席之地&#xff0c;用法简单&#xff0c;灵活&#xff0c;使用如下。但也带来一个问题&#xff0c;就是在单元测试时&#xff0c;Mock比较难办。public List<Goods> GetAllGoods(){using var con new SqlConnection();var sql &q…

收藏 | 分享 3 种脑洞大开的Excel技巧

全世界只有3.14 % 的人关注了数据与算法之美身为职场人&#xff0c;Excel基本是每天都会打开的软件&#xff0c;如果把对它的使用熟练程度分个等级&#xff0c;大概可以分为几下几种&#xff1a;Level 1&#xff1a;对Excel的基本功能已经有所了解&#xff0c;但还不熟练&#…