【五分鐘的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 主機添加咱們須要的任務項。就像下面的圖同樣:
好吧,此次廢話好像多了些。最後,偷偷說一句:創做不易,點個推薦吧.....