【5min+】后台任务的积木。.NetCore中的IHostedService
系列介绍
【五分钟的dotnet】是一个利用您的碎片化时间来学习和丰富.net知识的博文系列。它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net知识等等。
5min+不是超过5分钟的意思,"+"是知识的增加。so,它是让您花费5分钟以下的时间来提升您的知识储备量。
前言
这次终于可以给大家分享一些AspNet Core方面的东西了。虽然本次提及的内容是.NET Core通用,但将以AspNet Core为例作为介绍。
正文
咱们开发应用的时候,有时候可能需要建立一些独立于应用逻辑体本身的后台任务。比如:定时发送邮件、定时执行脚本这类持续运行的任务,也有验证数据库是否创建等只伴随应用启动而执行一次的任务。
在.NET Core 2.0 之后,官方为我们提供了一个叫做 IHostedService
的接口,它可以便于我们更好的实现托管服务。
在微软《.NET 微服务 - 体系结构》教程中,就有提及到关于该接口的描述:
那么今天咱们就来扒一扒 IHostedService
到底是一个怎样的东西,我们可以在什么情况下使用它。
前方车速够快,请抓好扶手。
IHostService
请注意 IHostedService
是从 .NET Core 提出的,所以可以看到它并不是专门只针对于 AspNet Core。 从.NetCore 3.x 之后,当大家创建一个新的AspNetCore应用的时候,打开默认的 Program.cs
文件,就会发现它和以往的版本已经不一样了。
//现在
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
//过去
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseKestrel()
.UseIISIntegration();
可以很明显的看出应用程序由原来的 IWebHostBuilder
更改为了 IHostBuilder
。这就告诉我们,.NET Core进行了更高层次的抽象,也就意味着现在能支持更多不同托管主机的创建方式,未来也将支持更多的类型。果然是一盘很大的棋啊
回到今天的主题 IHostedService
。 从命名上来看,就可以看出一些文章。 很明显,它是伴随主机一同启动的任务。因此来看看该接口的签名:
public interface IHostedService
{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
确实,很直观。只有两个方法,一个是启动,一个是停止。也就是说在 Host
启动的时候,就会调用 StartAsync
方法。在 Host
停止的时候就会调用 StopAsync
方法。
在AspNet Core中的作用
那么如果是咱们要在AspNet Core中使用它,该如何操作呢? 首先,咱们先来建立一个实现该接口的类:
public class DemoHostService : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
await Task.Delay(100);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
然后还需要在 Startup.cs
中将它进行注册:
services.AddHostedService<DemoHostService>();
OK,就完了。然后应用就会在启动的时候执行 StartAsync
方法。 咱们可以来断点试一试,看一看它的启动顺序。 经过断点之后我们发现基础的AspNet Core 应用会在执行完成 ConfigureServices
方法之后 再执行 DemoHostService
的 StartAsync
方法,最后再执行 Configure
方法:
// startup.cs
//第一步执行
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHostedService<DemoHostService>();
}
// 中间执行DemoHostService的StartAsync
// 最后执行
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints();
}
就如同下面的示意图一样,中间的部分就是咱们自定义的 HostService :
这就好玩了,说明在应用加载完成所有服务之后,就会在启动的时候开启所有的IHostedService
。 那么是否意味着我们可以在自定义的 IHostedService
使用DI容器中的服务呢,或者说在自定义任务中注入其它类。 答案是:肯定的。
public class DemoHostService : IHostedService
{
private IMyServiceDemo serviceDemo;
public DemoHostService(IMyServiceDemo IServiceDemo)
{
serviceDemo = IServiceDemo;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await Task.Delay(100);
}
}
就如同上面一样,我们使用了注入的IMyServiceDemo
类。但是,请注意!!!!:IHostedService
的生命周期为单例级别。所以只能在构造函数中注入同为单例级别的服务。而且就算 IHostedService
的周期为其它级别,比如(Scoped),它其实也无法直接在构造函数中注入非单例级别的服务。
理由是,HostService既然在Configure之前,就证明它目前所在的范围作用域还是在 “根” 级别上,所以当您注入一个非单例级别的类会提示您“无法在根范围获取一个对象”。
所以如果咱们需要获取其它生命周期类型服务的时候,就要使用另外一种方法:
public DemoHostService(IServiceProvider provider)
{
var serviceDemo = provider.CreateScope()
.ServiceProvider
.GetService<IMyScpoedService>();
}
上方只是个快捷写法,您在使用过程中一定要注意释放Scope。
在知道了IHostedService
之后,我们可以来想一想我们能够在伴随 Host 启动时,做一些什么事情呢? 比如,我们在应用启动时,可以对EFCore进行自动迁移和播种种子数据等:
public async Task StartAsync(CancellationToken cancellationToken)
{
using (var scope = _provider.CreateScope())
{
var efContext = scope.ServiceProvider.GetService<MyDbCotext>();
efContext.Database.EnsureCreated();
// Look for any students.
if (efContext.Students.Any())
{
return; // DB has been seeded
}
else
{
SeedData(efContext);
}
}
}
持续运行的后台服务
那么如果我们要定义一个持续运行的后台任务呢? 比如定时发送邮件等,是否直接在 IHostedService
的 StartAsync
中写个死循环呢? 好吧,答案是否定的。 如果这样咱们的Host就启动不起来。 通过查看 .NET Core Host的源代码就知道,它在最后启动的时候做了这样的事情:
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
foreach (var hostedService in _hostedServices)
{
// Fire IHostedService.Start
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
}
是的,它用了await关键字,也就是说如果直接写while死循环的话,就会导致一直等待而无法进行下面的操作。所以,我们可以在 IHostedService
的 StartAsync
中单独开一个线程来进行循环:
public Task StartAsync(CancellationToken cancellationToken)
{
new Task(() =>
{
while (true)
{
// doing
}
});
return Task.CompletedTask;
}
当然,.NET Core 早就想到了这一点,所以为我们提供了一个叫做 BackgroundService
的抽象类,我们只需要在 ExecuteAsync
方法中执行特有的逻辑就可以了:
public class MyBackgroundJob : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
SendEmail();
}
}
}
总结
IHostedService
接口为在 ASP.NET Core Web 应用程序(在 .NET Core 2.0 及更高版本中)或任何进程/主机(从使用 IHost 的 .NET Core 2.1 开始)中启动后台任务提供了一种便捷方式。 其主要优势在于,当主机本身将要关闭时,可以有机会进行正常取消以清理后台任务的代码。
其实关于后台定时任务,您可能会想到一些成熟的框架,比如Hangfire等。当然,它也为.NET Core版本提供了 IHostedService
的实现,您可以从这里看到它的实现。
偷偷告诉您,其实咱们的AspNetCore在启动时进行初始化Configure
等操作也是通过扩展一个IHostedService
来实现的,它的具体实现类叫做:GenericWebHostService
。
所以可以看出 IHostedService
为咱们提供了非常便利的操作,我们可以像累积木一样,往 Host 主机添加我们需要的任务项。就像下面的图一样:
好吧,这次废话好像多了些。最后,偷偷说一句:创作不易,点个推荐吧.....
【5min+】后台任务的积木。.NetCore中的IHostedService的更多相关文章
- .NetCore中的日志(2)集成第三方日志工具
.NetCore中的日志(2)集成第三方日志工具 0x00 在.NetCore的Logging组件中集成NLog 上一篇讨论了.NetCore中日志框架的结构,这一篇讨论一下.NetCore的Logg ...
- .NetCore中的日志(1)日志组件解析
.NetCore中的日志(1)日志组件解析 0x00 问题的产生 日志记录功能在开发中很常用,可以记录程序运行的细节,也可以记录用户的行为.在之前开发时我一般都是用自己写的小工具来记录日志,输出目标包 ...
- AutoMapper在asp.netcore中的使用
# AutoMapper在asp.netcore中的使用 automapper 是.net 项目中针对模型之间转换映射的一个很好用的工具,不仅提高了开发的效率还使代码更加简洁,当然也是开源的,htt ...
- netcore中的缓存介绍
Cache(缓存)是优化web应用的常用方法,缓存存放在服务端的内存中,被所有用户共享.由于Cache存放在服务器的内存中,所以用户获取缓存资源的速度远比从服务器硬盘中获取快,但是从资源占有的角度考虑 ...
- 在netcore中如何注入同一个接口的多个实现
netcore中自带了Ioc框架,这也影响了我们的编码习惯,以前都是静态类或者直接new对象,现在有了Ioc框架的支持,我们也不必守旧,应当使用起来,接受这种对象管理方式.使用过java的同仁,都习惯 ...
- .NetCore中EFCore的使用整理(二)-关联表查询
EF常用处理关联加载的方式有3中:延迟加载(Lazy Loading).贪婪加载 (Eager Loading)以及显示加载. 一.EF Core 1.1 1.当前的版本,还不支持延迟加载(Lazy ...
- .NetCore中EFCore for MySql整理(三)之Pomelo.EntityFrameworkCore.MySql
一.Pomelo.EntityFrameworkCore.MySql简介 Git源代码地址:https://github.com/PomeloFoundation/Pomelo.EntityFrame ...
- .NetCore中如何实现权限控制 基于Claim角色、策略、基于Claim功能点处理
.NetCore中如果实现权限控制的问题,当我们访问到一个Action操作的时候,我们需要进行权限控制 基于claims 角色控制 基于角色控制总觉得范围有点过大,而且控制起来感觉也不是太好,举一个例 ...
- .NetCore中EFCore for MySql整理(二)
一.简介 EF Core for MySql的官方版本MySql.Data.EntityFrameworkCore 目前正是版已经可用当前版本v6.10,对于以前的预览版参考:http://www.c ...
随机推荐
- java面试题-spring篇
这次是关于spring的面试题,和上次一样依旧挑了几个具有代表性的. 一. 谈谈你对 Spring 的理解 Spring 是一个开源框架,为简化企业级应用开发而生.Spring 可以是使简单的 Ja ...
- Sigmoid非线性激活函数,FM调频,胆机,HDR的意义
前几天家里买了个二手车子,较老,发现只有FM收音机,但音响效果不错,车子带蓝牙转FM,可以手机蓝牙播放音乐,但经过几次转换以及对FM的质疑,所以怀疑音质是否会剧烈下降,抱着试试的态度放了一个手机上的音 ...
- 《大道至简》第一章Java伪代码读后感
/*写程序,实际是一种方法论.从另外一个角度帮我们看待世界,看清事物的本质. 早在两千年前的寓言中,愚公和智叟的问答中就已体现整个工程的实现程序.*/ public class 移山{ string ...
- GP工作室-团队项目Beta冲刺
GP工作室-团队项目Beta冲刺 这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/GeographicInformationScience/ 这个作业要求 ...
- Java多态之Father f=new Son();
成员变量静态方法看左边,非静态方法编译看左边,运行看右边. 左边Father f其实是定义了一个Father类的对象,而右边new Son()可以只理解为是一个重写了Father类方法的对象. 因此, ...
- Java多线程:实现API接口创建线程方式详解
先看例子: /**实现Runnable接口创建线程步骤: * 1.创建一个实现Runnable接口的类 * 2.重写Runnable类中抽象的run()方法 * 3.创建实现类的对象 * 4.声明Th ...
- Java虚拟机系列一:一文搞懂 JVM 架构和运行时数据区
前言 之前写博客一直比较随性,主题也很随意,就是想到什么写什么,对什么感兴趣就写什么.虽然写起来无拘无束,自在随意,但也带来了一些问题,每次写完一篇后就要去纠结下一篇到底写什么,看来选择太多也不是好事 ...
- Java并发编程(一):线程基础知识以及synchronized关键字
1.线程与多线程的概念:在一个程序中,能够独立运行的程序片段叫作“线程”(Thread).多线程(multithreading)是指从软件或者硬件上实现多个线程并发执行的技术. 2.多线程的意义:多线 ...
- Hibernate(五)
================================criteria(QBC)查询========================QBC,(容器)又名对象查询:采用对象的方式(主要是cri ...
- 十二、sed文本处理
一.概述 1.sed 是一款流编辑工具,用来对文本进行过滤与替换工作,特别是当你想要对几十个配置文件做统计修改时,你会感受到 sed 的魅力!sed 通过输入读取文件内容,但一次仅读取一行内容进行某些 ...