• 【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 微服務 - 體系結構》教程中,就有提及到關于該接口的描述:

    x

    那么今天咱們就來扒一扒 IHostedService 到底是一個怎樣的東西,我們可以在什么情況下使用它。

    前方車速夠快,請抓好扶手。
    x

    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 方法之后 再執行 DemoHostServiceStartAsync 方法,最后再執行 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 :

    x

    這就好玩了,說明在應用加載完成所有服務之后,就會在啟動的時候開啟所有的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);
            }
        }
    }

    持續運行的后臺服務

    那么如果我們要定義一個持續運行的后臺任務呢? 比如定時發送郵件等,是否直接在 IHostedServiceStartAsync 中寫個死循環呢? 好吧,答案是否定的。 如果這樣咱們的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死循環的話,就會導致一直等待而無法進行下面的操作。所以,我們可以在 IHostedServiceStartAsync 中單獨開一個線程來進行循環:

    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 主機添加我們需要的任務項。就像下面的圖一樣:

    x

    好吧,這次廢話好像多了些。最后,偷偷說一句:創作不易,點個推薦吧.....

    x

    posted @ 2020-02-28 16:07  句幽  閱讀(...)  評論(...編輯  收藏
    贵州快三平台贵州快三主页贵州快三网站贵州快三官网贵州快三娱乐贵州快三开户贵州快三注册贵州快三是真的吗贵州快三登入贵州快三快三贵州快三时时彩贵州快三手机app下载贵州快三开奖 同仁县 | 县级市 | 隆回县 | 海南省 | 肥城市 | 壶关县 | 习水县 | 明光市 | 清涧县 | 永安市 | 罗甸县 | 新闻 | 苏尼特右旗 | 金沙县 | 延庆县 | 上栗县 | 台山市 | 墨竹工卡县 | 科技 | 治多县 | 连南 | 岳池县 | 广州市 | 泉州市 | 深州市 | 思茅市 | 新泰市 | 洪洞县 | 贵阳市 | 长汀县 | 定西市 | 商城县 | 阿瓦提县 | 江津市 | 克什克腾旗 | 印江 | 和林格尔县 | 钟山县 | 留坝县 | 宽城 | 闻喜县 | 平和县 | 临颍县 | 罗田县 | 封开县 | 清新县 | 宜章县 | 吉水县 | 云和县 | 泰来县 | 岫岩 | 琼海市 | 湖北省 | 广昌县 | 晋州市 | 惠来县 | 河曲县 | 武陟县 | 游戏 | 汨罗市 | 五峰 | 卢湾区 | 阳西县 | 金沙县 | 安国市 | 长宁区 | 慈溪市 | 长岛县 | 广州市 | 蕉岭县 | 衡东县 | 汕头市 | 孟州市 | 漳浦县 | 乌兰察布市 | 翼城县 | 彰化市 | 石屏县 | 内黄县 | 岢岚县 | 开远市 | 安溪县 | 桓仁 | 蓬莱市 | 门源 | 台东县 | 遂昌县 | 盐边县 | 互助 | 延川县 | 赤城县 | 苍南县 | 霞浦县 | 渑池县 | 蒲江县 | 万山特区 | 彰化市 | 娄烦县 | 深泽县 | 米脂县 | 乌鲁木齐县 | 阿勒泰市 | 大荔县 | 广州市 | 松原市 | 彭阳县 | 化州市 | 安泽县 | 丹巴县 | 涟水县 | 贵阳市 | 丹阳市 | 潮安县 | 中西区 | 当涂县 | 兴宁市 | 托克逊县 | 方城县 | 安顺市 | 南木林县 | 石楼县 | 靖宇县 | 金寨县 | 淳安县 | 通州市 | 乐亭县 | 沙湾县 | 台江县 | 南宁市 | 新泰市 | 阳泉市 | 榕江县 | 尤溪县 | 麟游县 | 垦利县 | 吉木乃县 | 大悟县 | 抚松县 | 那曲县 | 呼玛县 | 江北区 | 麦盖提县 | 岳西县 | 江津市 | 永年县 | 宁城县 | 黄山市 | 无为县 | 蒙城县 | 寿光市 | 阳东县 | 北流市 | 广州市 | 分宜县 | 左云县 | 西宁市 | 淳化县 | 京山县 | 兴化市 | 平安县 | 阳信县 | 巍山 | 新蔡县 | 西青区 | 自治县 | 临汾市 | 南漳县 | 定日县 | 东台市 | 潮州市 | 马关县 | 太原市 | 绥中县 | 罗源县 | 舒城县 | 舟山市 | 长岭县 | 肥东县 | 额济纳旗 | 万荣县 | 读书 | 长顺县 | 余干县 | 营口市 | 湟中县 | 仙桃市 | 太谷县 | 梁平县 | 兰溪市 | 马龙县 | 饶河县 | 宁远县 | 肃北 | 光山县 | 刚察县 | 阿鲁科尔沁旗 | 瓮安县 | 买车 | 万荣县 | 江阴市 | 永春县 | 永安市 | 睢宁县 | 鄂托克前旗 | 伊金霍洛旗 | 屏东市 | 驻马店市 |