写在前面
停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了。本文从源码角度进一步讨论.NET Core 3.0 
中关于Host扩展的一些技术点,主要讨论Long Run Program的创建与守护。
关于Host,我们最容易想到的就是程序的启动与停止,而其中隐藏着非常关键的功能,就是Host的初始化,我们所需要的所有资源都必须而且应该在程序启动过程中初始化完成,当然本文的主要内容并不是Host初始化,前文已经累述。当然,为了更好的守护与管理已经启动的Host,.NET 
Core 3.0将程序的生命周期事件的订阅开放给开发者,当然也包括自定义的Host Service对象。
注:本文代码基于.NET Core 3.0 Preview9
 
<https://img2018.cnblogs.com/blog/533598/201909/533598-20190914220234020-1611831647.png>
.NET Core 3.0中创建Long Run Program
IHost与IHostBuilder
当我们创建Long Run Program时,会首先关注程序的启动与停止,.NET Core 
3.0为此提供了一个接口IHost,该接口位于Microsoft.Extensions.Hosting类库中,其源码如下:
 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初始化时所注入的对象信息。
IHostBuilder接口所承担的核心功能就是程序的初始化,通过:IHost 
Build()来完成,当然只需要运行一次即可。其初始化内容一般包括以下几个功能:
 
<https://img2018.cnblogs.com/blog/533598/201909/533598-20190914220234654-1227722267.png>
另外需要说明的是,以上功能的初始化,是通过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: }
IHostService
文章开头有说过自定义Host 
Service对象,那么我们如何自定义呢,其实很简单只需要实现IHostService,并在ConfigureServices中调用services.AddHostedService<MyServiceA>()即可,以下是IHostService的源码:
 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: } 
根据源码我们可以知道,该接口只有两个方法,即代码程序开始与停止的方法。具体的实现可以参考如下:
 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托管的对象都必须要实现此接口。
Host生命周期的管理
该接口提供了一种我们可以在程序运行期间进行管理的功能,如程序的启动与停止事件的订阅,关于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的初始化过程。相对来说这段内容还是比较简单的,但是开发过程中,依然会遇到很多的问题,比如任务的定时机制、消息的接入、以及程序的性能优化等等,这些都需要我们在实践中进一步总结完善。
热门工具 换一换