专栏首页DotNet Core圈圈.NET Core 3.0之深入源码理解Host(二)

.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类库中,其源码如下:

/// <summary>
/// A program abstraction.
/// </summary>
public interface IHost : IDisposable
{
   /// <summary>
   /// The programs configured services.
   /// </summary>
   IServiceProvider Services { get; }
 
    /// <summary>
    /// Start the program.
    /// </summary>
    /// <param name="cancellationToken">Used to abort program start.</param>
    /// <returns>A <see cref="Task"/> that will be completed when the <see cref="IHost"/> starts.</returns>
    Task StartAsync(CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Attempts to gracefully stop the program.
    /// </summary>
    /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
    /// <returns>A <see cref="Task"/> that will be completed when the <see cref="IHost"/> stops.</returns>
    Task StopAsync(CancellationToken cancellationToken = default);
}

该接口含有一个只读属性:IServiceProvider Services { get; },通过该属性,我们可以拿到所有Host初始化时所注入的对象信息。

IHostBuilder接口所承担的核心功能就是程序的初始化,通过:IHost Build()来完成,当然只需要运行一次即可。其初始化内容一般包括以下几个功能:

另外需要说明的是,以上功能的初始化,是通过IHostBuilder提供的接口获取用户输入的信息后,通过调用Build()方法来完成初始化。以下为IHostBuilder的部分源代码:

/// <summary>
/// Set up the configuration for the builder itself. This will be used to initialize the <see cref="IHostEnvironment"/>
/// for use later in the build process. This can be called multiple times and the results will be additive.
/// </summary>
/// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
/// to construct the <see cref="IConfiguration"/> for the host.</param>
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
{
    _configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
    return this;
}
 
/// <summary>
/// Adds services to the container. This can be called multiple times and the results will be additive.
/// </summary>
/// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
/// to construct the <see cref="IConfiguration"/> for the host.</param>
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
{
    _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
    return this;
}
 
/// <summary>
/// Overrides the factory used to create the service provider.
/// </summary>
/// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>
/// <param name="factory">A factory used for creating service providers.</param>
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
{
    _serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(tory)));
    return this;
}
 
/// <summary>
/// Enables configuring the instantiated dependency container. This can be called multiple times and
/// the results will be additive.
/// </summary>
/// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>
/// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
/// to construct the <see cref="IConfiguration"/> for the host.</param>
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
{
    _configureContainerActions.Add(new ConfigureContainerAdapter<TContainerBuilder>(configureDelegate
        ?? throw new ArgumentNullException(nameof(configureDelegate))));
    return this;
}

IHostService

文章开头有说过自定义Host Service对象,那么我们如何自定义呢,其实很简单只需要实现IHostService,并在ConfigureServices中调用services.AddHostedService<MyServiceA>()即可,以下是IHostService的源码:

/// <summary>
/// Defines methods for objects that are managed by the host.
/// </summary>
public interface IHostedService
{
   /// <summary>
   /// Triggered when the application host is ready to start the service.
   /// </summary>
   /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
    Task StartAsync(CancellationToken cancellationToken);
 
    /// <summary>
    /// Triggered when the application host is performing a graceful shutdown.
    /// </summary>
    /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
    Task StopAsync(CancellationToken cancellationToken);
}

根据源码我们可以知道,该接口只有两个方法,即代码程序开始与停止的方法。具体的实现可以参考如下:

public class MyServiceA : IHostedService, IDisposable
{
   private bool _stopping;
   private Task _backgroundTask;

   public MyServiceA(ILoggerFactory loggerFactory)
   {
       Logger = loggerFactory.CreateLogger<MyServiceB>();
   }
 
    public ILogger Logger { get; }
 
    public Task StartAsync(CancellationToken cancellationToken)
    {
        Logger.LogInformation("MyServiceB is starting.");
        _backgroundTask = BackgroundTask();
        return Task.CompletedTask;
    }
 
    private async Task BackgroundTask()
    {
        while (!_stopping)
        {
            await Task.Delay(TimeSpan.FromSeconds(7));
            Logger.LogInformation("MyServiceB is doing background work.");
        }
 
        Logger.LogInformation("MyServiceB background task is stopping.");
    }
 
    public async Task StopAsync(CancellationToken cancellationToken)
    {
        Logger.LogInformation("MyServiceB is stopping.");
        _stopping = true;
        if (_backgroundTask != null)
        {
            // TODO: cancellation
            await _backgroundTask;
        }
    }
 
    public void Dispose()
    {
        Logger.LogInformation("MyServiceB is disposing.");
    }
}

IHostService是我们自定义Host管理对象的入口,所有需要压入到Host托管的对象都必须实现此接口。

Host生命周期的管理

该接口提供了一种我们可以在程序运行期间进行管理的功能,如程序的启动与停止事件的订阅,关于Host生命周期的管理,主要由IHostApplicationLifetime和IHostLifetime这两个接口来完成。

以下是IHostApplicationLifetime的源码

public interface IHostApplicationLifetime
{
   /// <summary>
   /// Triggered when the application host has fully started.
   /// </summary>
   CancellationToken ApplicationStarted { get; }

   /// <summary>
   /// Triggered when the application host is performing a graceful shutdown.
    /// Shutdown will block until this event completes.
    /// </summary>
    CancellationToken ApplicationStopping { get; }
 
    /// <summary>
    /// Triggered when the application host is performing a graceful shutdown.
    /// Shutdown will block until this event completes.
    /// </summary>
    CancellationToken ApplicationStopped { get; }
 
    /// <summary>
    /// Requests termination of the current application.
    /// </summary>
    void StopApplication();
}

IHostLifetime源码如下:

public interface IHostLifetime
{
   /// <summary>
   /// Called at the start of <see cref="IHost.StartAsync(CancellationToken)"/> which will wait until it's complete before
   /// continuing. This can be used to delay startup until signaled by an external event.
   /// </summary>
   /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
   /// <returns>A <see cref="Task"/>.</returns>
   Task WaitForStartAsync(CancellationToken cancellationToken);
 
    /// <summary>
    /// Called from <see cref="IHost.StopAsync(CancellationToken)"/> to indicate that the host is stopping and it's time to shut down.
    /// </summary>
    /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
    /// <returns>A <see cref="Task"/>.</returns>
    Task StopAsync(CancellationToken cancellationToken);
}

具体的使用可以参考如下代码:

public class MyLifetime : IHostLifetime, IDisposable
{
    .........
 
    private IHostApplicationLifetime ApplicationLifetime { get; }
 
    public ConsoleLifetime(IHostApplicationLifetime applicationLifetime)
    {
        ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
    }
 
    public Task WaitForStartAsync(CancellationToken cancellationToken)
    {
        _applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state =>
        {
            ((ConsoleLifetime)state).OnApplicationStarted();
        },
        this);
        _applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state =>
        {
            ((ConsoleLifetime)state).OnApplicationStopping();
        },
        this);
 
        .......
 
        return Task.CompletedTask;
    }
 
    private void OnApplicationStarted()
    {
        Logger.LogInformation("Application started. Press Ctrl+C to shut down.");
        Logger.LogInformation("Hosting environment: {envName}", Environment.EnvironmentName);
        Logger.LogInformation("Content root path: {contentRoot}", Environment.ContentRootPath);
    }
 
    private void OnApplicationStopping()
    {
        Logger.LogInformation("Application is shutting down...");
    }
 
    ........
}

总结

至此,我们知道了创建Long Run Program所需要关注的几个点,分别是继承IHostService、订阅程序的生命周期时间以及Host的初始化过程。相对来说这段内容还是比较简单的,但是开发过程中,依然会遇到很多的问题,比如任务的定时机制、消息的接入、以及程序的性能优化等等,这些都需要我们在实践中进一步总结完善。

本文分享自微信公众号 - DotNet技术平台(DotNetCore_Mements)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Swifter.Json 可能是 .Net 平台迄今为止性能最佳的 Json 序列化库【开源】

    Json (JavaScript Object Notation) 是一种轻量级的数据交换格式。它作为目前最欢迎的数据交换格式,也是各大开源贡献者的必争之地,如...

    梁规晓
  • .NET 程序员如何学习Vue

    之所以取这个标题,是因为本文来自内部培训的整理,培训的对象是公司的 .NET 程序员,.NET 程序员学习 Vue 是为了在项目中做二次开发时能够更好地跟产品对...

    oec2003
  • Entity Framework 和NHibernate的区别

    从个人感受上看,NHibernate显然是从上而下(Top-down)的方式,天然的POCO支持就是最好的佐证。而ADO.NET Entity Framewor...

    javascript.shop
  • 迁移 Azure Application Insights 到 .NET Core 3.0

    .NET Core 3.0 即将在本月的.NET Conf大会上发布正式版,在这之前包括我在内的不少朋友已经迫不及待使用预览版迁移了自己的应用,并爆得体无完肤。...

    Edi Wang
  • 如何快速创建定时任务【Quartz.NET总结系列一】

    前段时间,花了大量的时间,将原先的计划任务,切换到Quartz.NET来进行管理。原先的后台定时服务都是通过计划任务来实现的,但是随着业务增长,计划任务也越来越...

    章为忠
  • .NET CORE 怎么样从控制台中读取输入流

    从Console.ReadList/Read 的源码中,可学习到.NET CORE 是怎么样来读取输入流。

    梁规晓
  • 如何更改Json.NET的序列化规则

    我想要使序列化出来的JSON都是小写,可以通过建立 LowercaseContractResolver:DefaultContractResolver

    javascript.shop
  • 剑指offer之面试题2:实现Singleton模式

    在上述代码中,Singleton1的静态属性Instance中,只有在instance为null的时候才创建一个实例以避免重复创建。

    Vincent-yuan
  • 微软发布了开发社区采用.NET Standard的最新信息

    最近,微软发布了开发社区当前采用.NET Standard的最新信息。.NET Standard是API的正式规范,现有.NET实现在不同平台的是通用的(从而允...

    梁规晓
  • 高性能JavaScript

    原因:数据存储位置对大地代码整体性能会产生重要的影响,直接变量和局部变量的访问速度快于数组和对象成员。因为局部变量位于作用域链的第一个对象中,全局变量位于作用域...

    Tiffany_c4df

扫码关注云+社区

领取腾讯云代金券