.NET Core 3.0之深入源码理解Host(二)
写在前面
停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了。本文从源码角度进一步讨论.NET Core 3.0 中关于Host扩展的一些技术点,主要讨论Long Run Program的创建与守护。
关于Host,我们最容易想到的就是程序的启动与停止,而其中隐藏着非常关键的功能,就是Host的初始化,我们所需要的所有资源都必须而且应该在程序启动过程中初始化完成,本文的主要内容并不是Host初始化,前文已经累述。为了更好的守护与管理已经启动的Host,.NET Core 3.0将程序的生命周期事件的订阅开放给开发者,也包括自定义的Host Service对象。
注:本文代码基于.NET Core 3.0 Preview9

.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()来完成,当然只需要运行一次即可。其初始化内容一般包括以下几个功能:

另外需要说明的是,以上功能的初始化,是通过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的初始化过程。相对来说这段内容还是比较简单的,但是开发过程中,依然会遇到很多的问题,比如任务的定时机制、消息的接入、以及程序的性能优化等等,这些都需要我们在实践中进一步总结完善。
.NET Core 3.0之深入源码理解Host(二)的更多相关文章
- .NET Core 3.0之深入源码理解HttpClientFactory(二)
写在前面 上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpClientFactory是 ...
- .NET Core 3.0之深入源码理解Host(一)
写在前面 ASP .NET Core中的通用主机构建器是在v2.1中引入的,应用在启动时构建主机,主机作为一个对象用于封装应用资源以及应用程序启动和生存期管理.其主要功能包括配置初始化(包括加载配置以 ...
- .NET Core 3.0之深入源码理解Configuration(二)
文件型配置基本内容 上一篇文章讨论了Configuration的几个核心对象,本文继续讨论Configuration中关于文件型配置的相关内容.相比较而言,文件型配置的使用场景更加广泛,用户自定义 ...
- .NET Core 3.0之深入源码理解Startup的注册及运行
原文:.NET Core 3.0之深入源码理解Startup的注册及运行 写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...
- .NET Core 3.0之深入源码理解Configuration(一)
Configuration总体介绍 微软在.NET Core里设计出了全新的配置体系,并以非常灵活.可扩展的方式实现.从其源码来看,其运行机制大致是,根据其Source,创建一个Builder实例,并 ...
- .NET Core 3.0之深入源码理解Kestrel的集成与应用(一)
写在前面 ASP.NET Core 的 Web 服务器默认采用Kestrel,这是一个基于libuv(一个跨平台的基于Node.js异步I/O库)的跨平台.轻量级的Web服务器. 在开始之前,先回 ...
- .NET Core 3.0之深入源码理解Kestrel的集成与应用(二)
前言 前一篇文章主要介绍了.NET Core继承Kestrel的目的.运行方式以及相关的使用,接下来将进一步从源码角度探讨.NET Core 3.0中关于Kestrel的其他内容,该部分内容,我们 ...
- .NET Core 3.0之深入源码理解ObjectPool(一)
写在前面 对象池是一种比较常用的提高系统性能的软件设计模式,它维护了一系列相关对象列表的容器对象,这些对象可以随时重复使用,对象池节省了频繁创建对象的开销. 它使用取用/归还的操作模式,并重复执行这些 ...
- .NET Core 3.0之深入源码理解HealthCheck(一)
写在前面 我们的系统可能因为正在部署.服务异常终止或者其他问题导致系统处于非健康状态,这个时候我们需要知道系统的健康状况,而健康检查可以帮助我们快速确定系统是否处于正常状态.一般情况下,我们会提供公开 ...
随机推荐
- Python3源代码编译安装
Python3源代码编译安装 安装必要工具 yum-utils ,它的功能是管理repository及扩展包的工具 (主要是针对repository) $ sudo yum install yum-u ...
- postman->newman->jenkins构建过程的问题记录及解决方法
从postman导出请求集合后要做的工作: 需要调整导出的json文件,如配置环境变量{{host}},需要修改成准确的url; 通过newman执行newman run test_request.j ...
- (16)ASP.NET Core 通用主机(HostBuilder)
1.前言 ASP.NET Core应用程序可以配置和启动主机(Host).主机负责应用程序启动和生命周期管理.通用主机用于无法处理HTTP请求的应用程序.通用主机的用途是将HTTP管道从Web主机AP ...
- git 常规业务场景 使用
一般每个开发者都会有个自己的分支,有个test分支,合并代码用,两条分支相互备份,就算merge的时候被覆盖,也不用担心 建立自己的分支 // 创建本地分支, git checkout -b dev_ ...
- SpringBoot 内部方法调用,事务不起作用的原因及解决办法
在做业务开发时,遇到了一个事务不起作用的问题.大概流程是这样的,方法内部的定时任务调用了一个带事务的方法,失败后事务没有回滚.查阅资料后,问题得到解决,记录下来分享给大家. 场景 我在这里模拟一个场景 ...
- VS引用文件出现黄色感叹号丢失文件,应该如何解决?
VS是微软开发的一款超级强大的IDE,深受广大.net开发者喜爱. 但是再强大,也会有它的bug和缺点. 多人协同开发时,不知道你有没有遇到一个这样的情况:第二天上班,早早来到公司,打开电脑,拉取一下 ...
- fiddler抓HTTPS及APP请求的配置教程
一.fiddler安装 Fiddler官网下载Fiddler进行安装:https://www.telerik.com/fiddler 二.fiddler设置抓取HTTPS的请求 fiddler默认设置 ...
- 用Canvas实现Photoshop的钢笔工具(贝塞尔曲线)
前两天在用Canvas实现一个绘制路径的小功能.做完之后发现加以完善可以“复刻”一下PS里面的钢笔工具. PS里的钢笔工具对我来说是PS中最好用的工具! 所以本文主要介绍如何用Canvas来实现Pho ...
- Java反射使用总结
最近公司招了几名刚毕业的大学生,在给他们培训的过程中,讲到反射,他们有些人听不懂,对反射的概念云里雾里的,不知道反射有什么用. 因此就有了本文的诞生. 反射是java提供的一个重要功能,可以在运行时检 ...
- Linux 防火墙开放、查询、关闭端口
1. 开放指定端口 firewall-cmd --zone=public --add-port=5121/tcp --permanent # --permanent 永久生效,如果不加此条,重启后该命 ...