停了近一個月的技術博客,隨着正式脫離996的魔窟,接下來也正式恢復了。本文從源碼角度進一步討論.NET Core 3.0 中關於Host擴展的一些技術點,主要討論Long Run Program的建立與守護。html
關於Host,咱們最容易想到的就是程序的啓動與中止,而其中隱藏着很是關鍵的功能,就是Host的初始化,咱們所須要的全部資源都必須並且應該在程序啓動過程當中初始化完成,本文的主要內容並非Host初始化,前文已經累述。爲了更好的守護與管理已經啓動的Host,.NET Core 3.0將程序的生命週期事件的訂閱開放給開發者,也包括自定義的Host Service對象。性能優化
注:本文代碼基於.NET Core 3.0 Preview9app
當咱們建立Long Run Program時,會首先關注程序的啓動與中止,.NET Core 3.0爲此提供了一個接口IHost,該接口位於Microsoft.Extensions.Hosting類庫中,其源碼以下:async
1: /// <summary>
2: /// A program abstraction.
3: /// </summary>
4: public interface IHost : IDisposable
5: {
6: /// <summary>
7: /// The programs configured services.
8: /// </summary>
9: IServiceProvider Services { get; }
10:
11: /// <summary>
12: /// Start the program.
13: /// </summary>
14: /// <param name="cancellationToken">Used to abort program start.</param>
15: /// <returns>A <see cref="Task"/> that will be completed when the <see cref="IHost"/> starts.</returns>
16: Task StartAsync(CancellationToken cancellationToken = default);
17:
18: /// <summary>
19: /// Attempts to gracefully stop the program.
20: /// </summary>
21: /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
22: /// <returns>A <see cref="Task"/> that will be completed when the <see cref="IHost"/> stops.</returns>
23: Task StopAsync(CancellationToken cancellationToken = default);
24: }
該接口含有一個只讀屬性:IServiceProvider Services { get; },經過該屬性,咱們能夠拿到全部Host初始化時所注入的對象信息。ide
IHostBuilder接口所承擔的核心功能就是程序的初始化,經過:IHost Build()來完成,固然只須要運行一次便可。其初始化內容通常包括如下幾個功能:性能
另外須要說明的是,以上功能的初始化,是經過IHostBuilder提供的接口獲取用戶輸入的信息後,經過調用Build()方法來完成初始化。如下爲IHostBuilder的部分源代碼:優化
1: /// <summary>
2: /// Set up the configuration for the builder itself. This will be used to initialize the <see cref="IHostEnvironment"/>
3: /// for use later in the build process. This can be called multiple times and the results will be additive.
4: /// </summary>
5: /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
6: /// to construct the <see cref="IConfiguration"/> for the host.</param>
7: /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
8: public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
9: {
10: _configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
11: return this;
12: }
13:
14: /// <summary>
15: /// Adds services to the container. This can be called multiple times and the results will be additive.
16: /// </summary>
17: /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
18: /// to construct the <see cref="IConfiguration"/> for the host.</param>
19: /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
20: public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
21: {
22: _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
23: return this;
24: }
25:
26: /// <summary>
27: /// Overrides the factory used to create the service provider.
28: /// </summary>
29: /// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>
30: /// <param name="factory">A factory used for creating service providers.</param>
31: /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
32: public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
33: {
34: _serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
35: return this;
36: }
37:
38: /// <summary>
39: /// Enables configuring the instantiated dependency container. This can be called multiple times and
40: /// the results will be additive.
41: /// </summary>
42: /// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>
43: /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
44: /// to construct the <see cref="IConfiguration"/> for the host.</param>
45: /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
46: public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
47: {
48: _configureContainerActions.Add(new ConfigureContainerAdapter<TContainerBuilder>(configureDelegate
49: ?? throw new ArgumentNullException(nameof(configureDelegate))));
50: return this;
51: }
文章開頭有說過自定義Host Service對象,那麼咱們如何自定義呢,其實很簡單隻須要實現IHostService,並在ConfigureServices中調用services.AddHostedService<MyServiceA>()便可,如下是IHostService的源碼:ui
1: /// <summary>
2: /// Defines methods for objects that are managed by the host.
3: /// </summary>
4: public interface IHostedService
5: {
6: /// <summary>
7: /// Triggered when the application host is ready to start the service.
8: /// </summary>
9: /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
10: Task StartAsync(CancellationToken cancellationToken);
11:
12: /// <summary>
13: /// Triggered when the application host is performing a graceful shutdown.
14: /// </summary>
15: /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
16: Task StopAsync(CancellationToken cancellationToken);
17: }
根據源碼咱們能夠知道,該接口只有兩個方法,即代碼程序開始與中止的方法。具體的實現能夠參考以下:this
1: public class MyServiceA : IHostedService, IDisposable
2: {
3: private bool _stopping;
4: private Task _backgroundTask;
5:
6: public MyServiceA(ILoggerFactory loggerFactory)
7: {
8: Logger = loggerFactory.CreateLogger<MyServiceB>();
9: }
10:
11: public ILogger Logger { get; }
12:
13: public Task StartAsync(CancellationToken cancellationToken)
14: {
15: Logger.LogInformation("MyServiceB is starting.");
16: _backgroundTask = BackgroundTask();
17: return Task.CompletedTask;
18: }
19:
20: private async Task BackgroundTask()
21: {
22: while (!_stopping)
23: {
24: await Task.Delay(TimeSpan.FromSeconds(7));
25: Logger.LogInformation("MyServiceB is doing background work.");
26: }
27:
28: Logger.LogInformation("MyServiceB background task is stopping.");
29: }
30:
31: public async Task StopAsync(CancellationToken cancellationToken)
32: {
33: Logger.LogInformation("MyServiceB is stopping.");
34: _stopping = true;
35: if (_backgroundTask != null)
36: {
37: // TODO: cancellation
38: await _backgroundTask;
39: }
40: }
41:
42: public void Dispose()
43: {
44: Logger.LogInformation("MyServiceB is disposing.");
45: }
46: }
IHostService是咱們自定義Host管理對象的入口,全部須要壓入到Host託管的對象都必需要實現此接口。spa
該接口提供了一種咱們能夠在程序運行期間進行管理的功能,如程序的啓動與中止事件的訂閱,關於Host生命週期的管理,主要由IHostApplicationLifetime和IHostLifetime這兩個接口來完成。
如下是IHostApplicationLifetime的源碼
1: public interface IHostApplicationLifetime
2: {
3: /// <summary>
4: /// Triggered when the application host has fully started.
5: /// </summary>
6: CancellationToken ApplicationStarted { get; }
7:
8: /// <summary>
9: /// Triggered when the application host is performing a graceful shutdown.
10: /// Shutdown will block until this event completes.
11: /// </summary>
12: CancellationToken ApplicationStopping { get; }
13:
14: /// <summary>
15: /// Triggered when the application host is performing a graceful shutdown.
16: /// Shutdown will block until this event completes.
17: /// </summary>
18: CancellationToken ApplicationStopped { get; }
19:
20: /// <summary>
21: /// Requests termination of the current application.
22: /// </summary>
23: void StopApplication();
24: }
IHostLifetime源碼以下:
1: public interface IHostLifetime
2: {
3: /// <summary>
4: /// Called at the start of <see cref="IHost.StartAsync(CancellationToken)"/> which will wait until it's complete before
5: /// continuing. This can be used to delay startup until signaled by an external event.
6: /// </summary>
7: /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
8: /// <returns>A <see cref="Task"/>.</returns>
9: Task WaitForStartAsync(CancellationToken cancellationToken);
10:
11: /// <summary>
12: /// Called from <see cref="IHost.StopAsync(CancellationToken)"/> to indicate that the host is stopping and it's time to shut down.
13: /// </summary>
14: /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
15: /// <returns>A <see cref="Task"/>.</returns>
16: Task StopAsync(CancellationToken cancellationToken);
17: }
具體的使用能夠參考以下代碼:
1: public class MyLifetime : IHostLifetime, IDisposable
2: {
3: .........
4:
5: private IHostApplicationLifetime ApplicationLifetime { get; }
6:
7: public ConsoleLifetime(IHostApplicationLifetime applicationLifetime)
8: {
9: ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
10: }
11:
12: public Task WaitForStartAsync(CancellationToken cancellationToken)
13: {
14: _applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state =>
15: {
16: ((ConsoleLifetime)state).OnApplicationStarted();
17: },
18: this);
19: _applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state =>
20: {
21: ((ConsoleLifetime)state).OnApplicationStopping();
22: },
23: this);
24:
25: .......
26:
27: return Task.CompletedTask;
28: }
29:
30: private void OnApplicationStarted()
31: {
32: Logger.LogInformation("Application started. Press Ctrl+C to shut down.");
33: Logger.LogInformation("Hosting environment: {envName}", Environment.EnvironmentName);
34: Logger.LogInformation("Content root path: {contentRoot}", Environment.ContentRootPath);
35: }
36:
37: private void OnApplicationStopping()
38: {
39: Logger.LogInformation("Application is shutting down...");
40: }
41:
42: ........
43: }
至此,咱們知道了建立Long Run Program所須要關注的幾個點,分別是繼承IHostService、訂閱程序的生命週期時間以及Host的初始化過程。相對來講這段內容仍是比較簡單的,可是開發過程當中,依然會遇到不少的問題,好比任務的定時機制、消息的接入、以及程序的性能優化等等,這些都須要咱們在實踐中進一步總結完善。