系列介紹
【五分鍾的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 主機添加我們需要的任務項。就像下面的圖一樣:
好吧,這次廢話好像多了些。最后,偷偷說一句:創作不易,點個推薦吧.....