Asp.net Core 2.1新功能Generic Host(通用主機),瞭解一下

什麼是Generic Host ?

image

 

這是在Asp.Net Core 2.1加入了一種新的Host,如今2.1版本的Asp.Net Core中,有了兩種可用的Host。git

  • Web Host –適用於託管Web程序的Host,就是咱們所熟悉的在Asp.Net Core應用程序的Mai函數中用CreateWebHostBuilder建立出來的經常使用的WebHost。

clip_image001

  • Generic Host (ASP.NET Core 2.1版本纔有) – 適用於託管非 Web 應用(例如,運行後臺任務的應用)。 在將來的版本中,通用主機將適用於託管任何類型的應用,包括 Web 應用。 通用主機最終將取代 Web 主機,這大概也是這種類型的主機叫作通用主機的緣由,在本博客中,咱們將結合源碼,討論通用主機的工做原理。

爲何要用通用主機?

通用主機,讓我能夠用編寫Asp.Net Core的思想(例如控制反轉、依賴注入、IOC容器)來簡化控制檯應用程序的建立(我的看法),主機負責程序的啓動和生存週期的管理,這對於不處理HTTP請求的應用程序很是有用(處理HTTP請求的是Web應用程序,用Web Host託管),通用主機的目標是將HTTP管道從Web Host中脫離出來,使得Asp.Net Core的那套東西也適用於其餘.Net Core程序。github

Demo下載

在開始跟隨我分析通用主機以前,你們能夠到Github下載這個官方Demo。web

https://github.com/aspnet/Docs/tree/master/aspnetcore/fundamentals/host/generic-host/samples/json

若是以爲下載一整個比較慢,能夠從個人這個Github倉庫下載,沒有其餘多餘內容,國內Github比較慢,若是你從官方那個倉庫下載可能會須要很長時間甚至失敗。app

https://github.com/liuzhenyulive/Generic-Host-Demoasync

 

Generic Host 和Web Host 對比

首先,你們打開下載下來的這個官方Demo,進入Main函數。函數

image

能夠看到,這簡直就是一個精簡版的Asp.Net Core應用程序,對這個Main函數中出現的全部方法,你們對Asp.Net Core Web應用程序比較熟悉,因此我與Asp.net core 的Webhost作了一個對比,來幫助你們找找感受。源碼分析

通用主機 Web主機
new HostBuilder() WebHost.CreateDefaultBuilder(args)
ConfigureAppConfiguration
(用於配置Configuration)
WebHost也有這個方法,只是你們默承認能沒有調用。
image
ConfigureServices
(用於配置Service,也就是依賴注入)
WebHost其實也有ConfigureServices方法,能夠這麼調用。
image
可是咱們通常不多這麼用,通常都是放在Startup的ConfigureServices方法中進行依賴注入。
ConfigureLogging
(是本應用程序所須要的配置,非必需)
WebHost仍是有!
image
builder.RunConsoleAsync()
image
RunConsoleAsync中實際上是對hostbuilder進行
Builder而後Run
CreateWebHostBuilder(args).Build().Run();

也就是Main函數中的Build().Run();

無無無無
Startup中的Configure()方法
Asp.net core在此方法中進行Http請求管道的配置

 

綜上對比,我作了以下歸納!ui

  1. 通用主機(Generic Host)有的 Web Host都有。
  2. Web Host的Http Pipeline即Startup.Configure() 在通用主機中沒有。

這就應證了開頭所說的:通用主機的目標是將HTTP管道從Web Host中脫離出來,使得Asp.Net Core的那套東西也適用於其餘.Net Core程序。this

如何使用?

Run函數解讀

我以爲要知道怎麼用,那麼咱們就首先要知道Host的Run方法內究竟是在執行什麼?

因此咱們深刻源碼,一路F12!

builder.RunConsoleAsync(); =>hostBuilder.UseConsoleLifetime().Build().RunAsync(cancellationToken);=> await host.StartAsync(token);

總算找到了,最關鍵的在這裏。

public async Task StartAsync(CancellationToken cancellationToken = default (CancellationToken))
    {
      this._logger.Starting();
      TaskCompletionSource<object> completionSource1 = new TaskCompletionSource<object>();

      ref CancellationToken local = ref cancellationToken;
      TaskCompletionSource<object> completionSource2 = completionSource1;
      local.Register((Action<object>) (obj => ((TaskCompletionSource<object>) obj).TrySetCanceled()), (object) completionSource2);

      IHostLifetime hostLifetime1 = this._hostLifetime;
      TaskCompletionSource<object> completionSource3 = completionSource1;
      hostLifetime1.RegisterDelayStartCallback((Action<object>) (obj => ((TaskCompletionSource<object>) obj).TrySetResult((object) null)), (object) completionSource3);

      IHostLifetime hostLifetime2 = this._hostLifetime;
      ApplicationLifetime applicationLifetime = this._applicationLifetime;
      hostLifetime2.RegisterStopCallback((Action<object>) (obj => (obj as IApplicationLifetime)?.StopApplication()), (object) applicationLifetime);

      object task = await completionSource1.Task;
      
this._hostedServices = this.Services.GetService<IEnumerable<IHostedService>> (); foreach (IHostedService hostedService in this ._hostedServices) await hostedService.StartAsync(cancellationToken).ConfigureAwait(false
);

      this._applicationLifetime?.NotifyStarted();
      this._logger.Started();
    }

 

知道你們都喜歡Yellow色,因此我用Yellow把最關鍵的代碼標示出來了,那麼這些代碼有什麼含義呢?

this._hostedServices = this.Services.GetService<IEnumerable<IHostedService>>();

這一行的意思是,從容器中取出全部實現了IHostedService的服務。

這就意味着,咱們實現了IHostedService後,須要把該Service註冊到IOC容器中。

foreach (IHostedService hostedService in this._hostedServices)
      await hostedService.StartAsync(cancellationToken).ConfigureAwait(false);

執行每一個服務的StartAsync方法。

因此,你們是否是冥冥中猜到了怎麼用的呢?大聲笑

我總結的步驟以下:

  1. 自定義一個Service,繼承 IHostedService接口。
  2. 實現 IHostedService的StartAsync方法,把須要執行的任務放到這個方法中。
  3. 把該服務註冊到IOC容器(ServiceCollection)中。

自定義任務的運行

對於步驟1和2,對應的代碼以下:

public class PrintTextToConsoleService : IHostedService, IDisposable
    {
        private readonly ILogger _logger;
        private readonly IOptions<AppConfig> _appConfig;
        private Timer _timer;

        public PrintTextToConsoleService(ILogger<PrintTextToConsoleService> logger, IOptions<AppConfig> appConfig)
        {
            _logger = logger;
            _appConfig = appConfig;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Starting");

            _timer = new Timer(DoWork, null, TimeSpan.Zero,
                TimeSpan.FromSeconds(5));

            return Task.CompletedTask;
        }

        private void DoWork(object state)
        {
            _logger.LogInformation($"Background work with text: {_appConfig.Value.TextToPrint}");
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Stopping.");

            _timer?.Change(Timeout.Infinite, 0);

            return Task.CompletedTask;
        }

        public void Dispose()
        {
            _timer?.Dispose();
        }
    }

能夠看到,在StartAsync中,定義了一個定時任務,帶定時任務每五秒執行一次DoWork方法。

在DoWork方法中,日誌記錄器記錄了一段內容。

由於在Main方法中,對Log進行了以下的配置。

image

因此,一旦日誌記錄了內容,該內容就會在控制檯中輸出。

對於步驟3,對應的代碼以下

public static async Task Main(string[] args)
        {
            var builder = new HostBuilder()    //實例化一個通用主機
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    config.AddJsonFile("appsettings.json", optional: true);
                    config.AddEnvironmentVariables();

                    if (args != null)
                    {
                        config.AddCommandLine(args);
                    }
                }) //配置Configuration
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddOptions();
                    services.Configure<AppConfig>(hostContext.Configuration.GetSection("AppConfig"));
                    services.AddSingleton
<IHostedService, PrintTextToConsoleService>
();
                })    //配置Service (依賴注入)
                .ConfigureLogging((hostingContext, logging) => {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    logging.AddConsole();
                });   //配置Log (本項目中要利用Log把內容在控制檯輸出)

            await builder.RunConsoleAsync();    //在控制檯應用程序中運行通用主機
        }

黃色部分,把實現了IHostedService接口的PrintTextToConsoleService註冊到容器中。

 

F5 運行

image

能夠看到,控制檯中,每五秒就有一次內容輸出,說明DoWork方法沒五秒被執行了一次,也說明PrintTextToConsoleService的StartAsync被成功調用了。

 

但願本文對幫助你們理解通用主機可以有所幫助,若是對.Net Core的源碼分析、潮流新技術感興趣

歡迎關注我

不按期推出實用幹活,謝謝!

參考文獻

https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.1

相關文章
相關標籤/搜索