.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(一)
写在前面 我们的系统可能因为正在部署.服务异常终止或者其他问题导致系统处于非健康状态,这个时候我们需要知道系统的健康状况,而健康检查可以帮助我们快速确定系统是否处于正常状态.一般情况下,我们会提供公开 ...
随机推荐
- (16)ASP.NET Core 通用主机(HostBuilder)
1.前言 ASP.NET Core应用程序可以配置和启动主机(Host).主机负责应用程序启动和生命周期管理.通用主机用于无法处理HTTP请求的应用程序.通用主机的用途是将HTTP管道从Web主机AP ...
- 多线程 共享资源 同步锁 java
Java多线程编程:Lock synchronized是java中的一个关键字,也就是说是Java语言内置的特性.那么为什么会出现Lock呢? 如果一个代码块被synchronized修饰了,当一 ...
- 认识Redies
既然是作为了解性文章,那必然不会做很深入的解读.深入的解读以后会加上. 我们先来回答两个问题.通过这两个问题来开始我们的Redies入门之旅. Redies是什么? Redies有什么作用? Redi ...
- Codeforces 343D Water Tree
题意简述 维护一棵树,支持以下操作: 0 v:将以v为跟的子树赋值为1 1 v:将v到根节点的路径赋值为0 2 v:询问v的值 题解思路 树剖+珂朵莉树 代码 #include <set> ...
- python骚操作---Print函数用法
---恢复内容开始--- python骚操作---Print函数用法 在 Python 中,print 可以打印所有变量数据,包括自定义类型. 在 3.x 中是个内置函数,并且拥有更丰富的功能. 参数 ...
- c# 20160721
ctrl y =>反撤销 ctrl m m 隐藏当前代码段 重载运算符语法 把事件处理程序注册为 click事件的监听程序 [newButton.click+=newButton_click] ...
- 使用 media 实现响应式布局
最近工作有一个需求是将一个界面改为响应式布局,由于UI还没有给设计,于是自己先查了一下资料做了一个demo.其实实现响应式布局的方式有很多,利用media实现就是其中一种,但是他也有一些缺点,比如说要 ...
- Jenkins教程——从安装到部署Docker服务(一)安装Jenkins
前言 写了好久Jenkins的脚本了,写得虽然不是太好,但是可以基本满足需要,最近比较忙,这个月也没怎么更新博客,开始更新Jenkins这个系列的文章,打算从安装Jenkins -> 流水线工程 ...
- Spring学习之旅(十)--MockMvc
在之前的 Spring学习之旅(八)--SpringMVC请求参数 我们是通过在控制台输出来验证参数是否正确,但是这样做实在是太耗时间了,我们今天来学习下 MockMvc,它可以让我们不需要启动项目就 ...
- Ranger-hdfs插件压测
公司的并发量很大,担心使用ranger-hdfs插件后 namenode扛不住压力 所以需要本人进行压测 没有在网上找到其他文档 资料太少了 决定自己写一份 介绍下压测环境 15个节点 集群高可用两个 ...