写在前面

停了近一个月的技术博客,随着正式脱离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(二)的更多相关文章

  1. .NET Core 3.0之深入源码理解HttpClientFactory(二)

      写在前面 上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpClientFactory是 ...

  2. .NET Core 3.0之深入源码理解Host(一)

    写在前面 ASP .NET Core中的通用主机构建器是在v2.1中引入的,应用在启动时构建主机,主机作为一个对象用于封装应用资源以及应用程序启动和生存期管理.其主要功能包括配置初始化(包括加载配置以 ...

  3. .NET Core 3.0之深入源码理解Configuration(二)

      文件型配置基本内容 上一篇文章讨论了Configuration的几个核心对象,本文继续讨论Configuration中关于文件型配置的相关内容.相比较而言,文件型配置的使用场景更加广泛,用户自定义 ...

  4. .NET Core 3.0之深入源码理解Startup的注册及运行

    原文:.NET Core 3.0之深入源码理解Startup的注册及运行   写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...

  5. .NET Core 3.0之深入源码理解Configuration(一)

    Configuration总体介绍 微软在.NET Core里设计出了全新的配置体系,并以非常灵活.可扩展的方式实现.从其源码来看,其运行机制大致是,根据其Source,创建一个Builder实例,并 ...

  6. .NET Core 3.0之深入源码理解Kestrel的集成与应用(一)

      写在前面 ASP.NET Core 的 Web 服务器默认采用Kestrel,这是一个基于libuv(一个跨平台的基于Node.js异步I/O库)的跨平台.轻量级的Web服务器. 在开始之前,先回 ...

  7. .NET Core 3.0之深入源码理解Kestrel的集成与应用(二)

      前言 前一篇文章主要介绍了.NET Core继承Kestrel的目的.运行方式以及相关的使用,接下来将进一步从源码角度探讨.NET Core 3.0中关于Kestrel的其他内容,该部分内容,我们 ...

  8. .NET Core 3.0之深入源码理解ObjectPool(一)

    写在前面 对象池是一种比较常用的提高系统性能的软件设计模式,它维护了一系列相关对象列表的容器对象,这些对象可以随时重复使用,对象池节省了频繁创建对象的开销. 它使用取用/归还的操作模式,并重复执行这些 ...

  9. .NET Core 3.0之深入源码理解HealthCheck(一)

    写在前面 我们的系统可能因为正在部署.服务异常终止或者其他问题导致系统处于非健康状态,这个时候我们需要知道系统的健康状况,而健康检查可以帮助我们快速确定系统是否处于正常状态.一般情况下,我们会提供公开 ...

随机推荐

  1. elk系列教程:docker中安装配置elk

    elasticSearch Docker安装elasticsearch: docker pull docker.io/elasticsearch:7.2.0 启动: docker run -p 920 ...

  2. JVM总结(三)

    JVM总结(3)Class文件,类加载机制.编译过程 Java编译器先把Java代码编译为存储字节码的Class文件,再通过Class文件进行类加载. Class类文件的结构 Java编译器可以把Ja ...

  3. Go中的异常处理

    1. errors包 Go 有一个预先定义的 error 接口类型 : type error interface { Error() string } 错误值用来表示异常状态.Go也提供了一个包:er ...

  4. Scala基础语法学习(一)

    1. val和var的区别 val定义的是一个常量,无法改变其内容 scala> val s = 0 s: Int = 0 scala> s = 2 <console>:12: ...

  5. 以kaldi中的yesno为例谈谈transition

    在基于GMM-HMM的传统语音识别里,比音素(phone)更小的单位是状态(state).一般每个音素由三个状态组成,特殊的是静音(SIL)由五个状态组成.这里所说的状态就是指HMM里的隐藏的状态,而 ...

  6. 认识Linux工具

    Centos7镜像网站:清华,阿里,网易 软件安装:lamp   httpd (认识) yum: 安装工具    需要选版本和特性,所以生产不用yum rpm:安装依赖 源码编译 shell脚本:yu ...

  7. python 之 前端开发(基本选择器、组合选择器、 交集与并集选择器、序列选择器、属性选择器、伪类选择器、伪元素选择器)

    11.3 css 11.31 基本选择器 11.311 id选择器 根据指定的id名称,在当前界面中找到对应的唯一一个的标签,然后设置属性 <!DOCTYPE html> <html ...

  8. c语言实现基本的数据结构(一) 线性表

    #include <stdio.h> #include <tchar.h> #include <stdlib.h> #define LIST_INIT_SIZE 1 ...

  9. ORACLE中添加删除主键

    本文转自:http://blog.chinaunix.net/uid-17079336-id-2832443.html 1.创建表的同时创建主键约束(1)无命名create table student ...

  10. 编程杂谈——Platform target x64

    在Visual Studio中选择.NET Framework框架并选用任意模板创建一个普通的Web应用工程,毫无疑问,此时应该是能够正常运行此工程的. 但是将工程属性->编译->Plat ...