【五分钟的dotnet】是一个利用您的碎片化时间来学习和丰富.net知识的博文系列。它所包含了.net体系中可能会涉及到的方方面面,好比C#的小细节,AspnetCore,微服务中的.net知识等等。
5min+不是超过5分钟的意思,"+"是知识的增长。so,它是让您花费5分钟如下的时间来提高您的知识储备量。git
此次终于能够给你们分享一些AspNet Core方面的东西了😀。虽然本次说起的内容是.NET Core通用,但将以AspNet Core为例做为介绍。github
我们开发应用的时候,有时候可能须要创建一些独立于应用逻辑体自己的后台任务。好比:定时发送邮件、定时执行脚本这类持续运行的任务,也有验证数据库是否建立等只伴随应用启动而执行一次的任务。web
在.NET Core 2.0 以后,官方为咱们提供了一个叫作 IHostedService
的接口,它能够便于咱们更好的实现托管服务。数据库
在微软《.NET 微服务 - 体系结构》教程中,就有说起到关于该接口的描述:c#
那么今天我们就来扒一扒 IHostedService
究竟是一个怎样的东西,咱们能够在什么状况下使用它。app
前方车速够快,请抓好扶手。
框架
请注意 IHostedService
是从 .NET Core 提出的,因此能够看到它并非专门只针对于 AspNet Core。 从.NetCore 3.x 以后,当你们建立一个新的AspNetCore应用的时候,打开默认的 Program.cs
文件,就会发现它和以往的版本已经不同了。async
//如今 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进行了更高层次的抽象,也就意味着如今能支持更多不一样托管主机的建立方式,将来也将支持更多的类型。果真是一盘很大的棋啊🤫ide
回到今天的主题 IHostedService
。 从命名上来看,就能够看出一些文章。 很明显,它是伴随主机一同启动的任务。所以来看看该接口的签名:函数
public interface IHostedService { Task StartAsync(CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken); }
确实,很直观。只有两个方法,一个是启动,一个是中止。也就是说在 Host
启动的时候,就会调用 StartAsync
方法。在 Host
中止的时候就会调用 StopAsync
方法。
那么若是是我们要在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 主机添加咱们须要的任务项。就像下面的图同样:
好吧,此次废话好像多了些。最后,偷偷说一句:创做不易,点个推荐吧.....