天天看點

Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?

什麼是Generic Host ?

Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?

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

  • Web Host –适用于托管Web程式的Host,就是我們所熟悉的在Asp.Net Core應用程式的Mai函數中用CreateWebHostBuilder建立出來的常用的WebHost。
Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?
  • 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程式。

Demo下載下傳

在開始跟随我分析通用主機之前,大家可以到Github下載下傳這個官方Demo。

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

如果覺得下載下傳一整個比較慢,可以從我的這個Github倉庫下載下傳,沒有其他多餘内容,國内Github比較慢,如果你從官方那個倉庫下載下傳可能會需要很長時間甚至失敗。

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

Generic Host 和Web Host 對比

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

Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?

可以看到,這簡直就是一個精簡版的Asp.Net Core應用程式,對這個Main函數中出現的所有方法,大家對Asp.Net Core Web應用程式比較熟悉,是以我與Asp.net core 的Webhost做了一個對比,來幫助大家找找感覺。

通用主機 Web主機
new HostBuilder() WebHost.CreateDefaultBuilder(args)

ConfigureAppConfiguration

(用于配置Configuration)

WebHost也有這個方法,隻是大家預設可能沒有調用。
Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?

ConfigureServices

(用于配置Service,也就是依賴注入)

WebHost其實也有ConfigureServices方法,可以這麼調用。
Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?
但是我們一般很少這麼用,一般都是放在Startup的ConfigureServices方法中進行依賴注入。

ConfigureLogging

(是本應用程式所需要的配置,非必需)

WebHost還是有!
Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?
builder.RunConsoleAsync()
Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?

RunConsoleAsync中其實是對hostbuilder進行

Builder然後Run

CreateWebHostBuilder(args).Build().Run();

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

無無無無

Startup中的Configure()方法

Asp.net core在此方法中進行Http請求管道的配置

綜上對比,我做了如下概括!

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

這就應證了開頭所說的:通用主機的目标是将HTTP管道從Web Host中脫離出來,使得Asp.Net Core的那套東西也适用于其他.Net Core程式。

如何使用?

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方法。

是以,大家是不是冥冥中猜到了怎麼用的呢?

Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?

我總結的步驟如下:

  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進行了如下的配置。

Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?

是以,一旦日志記錄了内容,該内容就會在控制台中輸出。

對于步驟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 運作

Asp.net Core 2.1新功能Generic Host(通用主機),了解一下Generic Host 和Web Host 對比如何使用?

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

希望本文對幫助大家了解通用主機能夠有所幫助,如果對.Net Core的源碼分析、潮流新技術感興趣

歡迎關注我

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

參考文獻

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

繼續閱讀