冠军

导航

理解 ASP.NET Core: Host

dotnet core 非常好用,代码也及其精炼,但是,你真的搞懂了每一行代码背后的含义了吗?

本文希望能够深入浅出地梳理一下它的脉络,把它从神秘变成水晶一般透明。

本文关注于分析 Pragram.cs 代码文件,深入分析其中的 Host 宿主处理机制。

新创建 Web 应用程序

使用下面的命令可以快速创建一个 dotnet core 的 Web 应用。

dotenet new web -n HelloWeb

生成的 Program.cs 中源代码如下所示:

namespace HelloWeb
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Main 方法调用了自定义的 CreteaHostBuilder() 方法来得到一个 IHostBuilder 对象实例,并在调用其 Build() 方法来得到实际的 Host 对象,然后调用 Host 对象实例的 Run() 方法开始应用的执行过程。

在这个 CreateHostBuilder() 方法中,通过 Host 类型的静态方法 CreateDefaultBuilder() 来得到 IHostBuilder 对象实例,并调用了此对象实例的 ConfigureWebHostDefaults() 方法。此方法返回的仍然是 IHostBuilder 实例本身。

通过类型名称可以看出,这里使用了构建模式,这个 IHostBuilder 对象是用来构建一个 IHost 对象实例的,Build() 方法即构建出一个 IHost 对象实例,然后的 Run() 方法是调用了 IHost 对象实例的方法。

作为学习 Host 的第一篇文章,我们先关注 Host 本身,以后我们再看 IHostBuilder 是怎样构建这个 Host 对象实例的。

Host

我们就从 Host 开始。

Host 本身作为整个应用程序的基石,主要作用是用来管理寄宿在自身之上的 IHostedService,负责把注册的这些服务启动起来,并提供应用程序的生命周期管理。

具体注册了哪些服务,其实是通过 IHostBuilder 来完成的,所以,对于 Host 本身来说,并没有提供多少扩展点,我们主要是理解它提供的功能为主。

在 ASP.NET Core 中,Host 是整个应用的基石,Web 应用也是作为一个服务是寄宿在这个 Host 之上的。所以,我们先跳过 IHostBuilder,首先从 IHost 开始。

Host 使用接口 IHost 定义,此接口的定义很简洁,可以在 GitHub 中查看 IHost 源码

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Extensions.Hosting
{
    /// <summary>
    /// A program abstraction.
    /// </summary>
    public interface IHost : IDisposable
    {
        IServiceProvider Services { get; }

        Task StartAsync(CancellationToken cancellationToken = default);
        Task StopAsync(CancellationToken cancellationToken = default);
    }
}

其中 类型为 IServiceProvider 的 Services 用来提供依赖注入的支持,我们有专门介绍依赖注入的文章,请参考这里,这里我们需要知道的就是,可以通过它来获取对象实例。

两个方法分别用来启动服务和停止服务。服务这个词在 .NET Core 中在不同的场景下,有不同的含义,这里指的服务是 IHostedService 服务,也就是寄宿在 Host 中的服务,启动服务就是调用 IHostedService 对象的启动方法,而停止服务也就是调用这些服务的停止方法。

这些服务很可能运行在不同的线程之上,我们怎么通知一个线程优雅地结束掉,而不是粗暴地直接取消呢?在多线程模式下,我们一般会传递一个 CancellationToken 对象进去,通过它实现线程之间的通知。

不要被名字中的 Cancelation 所迷惑,它并不是仅仅用来取消操作的,更多的时候,是用来在多线程场景下,在不同的线程之间进行事件通知的。我们也会专门介绍这个 CancellationToken,这里先不深入进行了。

除了核心的 StartAsync() 和 StopAsync() 方法,另外,在 Host 的扩展方法定义 HostingAbstractionsHostExtensions 中,又定义了一组辅助方法:

  • void Run(this IHost host)
  • async Task RunAsync(this IHost host, CancellationToken token = default)
  • void Start(this IHost host)
  • Task StopAsync(this IHost host, TimeSpan timeout)
  • void WaitForShutdown(this IHost host)
  • async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default)

这里面提供的的 Run() 调用内部调用了 RunAsync() 方法,而 RunAsync() 方法又调用了 StartAsync() 方法用来启动 Host 主机。

public static class HostingAbstractionsHostExtensions
{
    // ......

    public static void Run(this IHost host)
    {
        host.RunAsync().GetAwaiter().GetResult();
    }

    public static async Task RunAsync(this IHost host, CancellationToken token = default)
    {
        try
        {
            await host.StartAsync(token);

            await host.WaitForShutdownAsync(token);
        }
        finally
        {
            if (host is IAsyncDisposable asyncDisposable)
            {
                await asyncDisposable.DisposeAsync();
            }
            else
            {
                host.Dispose();
            }
        }
    }
	// ......
}

同样来自扩展方法的 WaitForShutdownAsync() 方法,提供了对 Host 停机的监控。

IHsot 接口定义的默认实现 Host

IHost 接口的默认实现是 Host 类,可以在 GitHub 中查看源码。注意,它的命名空间是 Microsoft.Extensions.Hosting.Internal,就是说,这一个内部类,并不能在外部实例化。我们在主程序中看到的 Host 并不是这个 Host 类,那是另外一个 Host,我们后面介绍。

namespace Microsoft.Extensions.Hosting.Internal
{
    internal class Host : IHost, IAsyncDisposable
    {

查看这个 Host 的构造函数,可以看到它依赖多个服务的支持,其中最为重要的是两个生命周期管理服务。

  • 主机的生命周期管理
  • 应用的生命周期管理

在 Host 的成员中,有两个重要的成员

  • IHostLifetime
  • IHostApplicationLifetime

可以看到,它们都是从构造函数中注入的,在 dotnet core 中,构造中的参数应当看作服务依赖,也就是它所依赖的服务,这些被依赖的服务会在容器构建对象时,由容器提供。

public Host(
    IServiceProvider services, 
    IHostApplicationLifetime applicationLifetime, 
    ILogger<Host> logger,
    IHostLifetime hostLifetime, 
    IOptions<HostOptions> options)
{
    Services = services ?? throw new ArgumentNullException(nameof(services));
    _applicationLifetime = (applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime))) as ApplicationLifetime;
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    _hostLifetime = hostLifetime ?? throw new ArgumentNullException(nameof(hostLifetime));
    _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}

主机的生命周期管理

这里需要注意的是,应用实际上分为了两个层级,应用的宿主 Host 和应用 Application。
宿主 Host 是底层,它的生命周期因为寄宿的环境不同而存在多种形式。例如,当寄宿在 Console 下面的时候,这是默认情况,通过执行程序来启动 Host,通过 Ctrl + C 可以终止 Host 的运行,进而导致整个应用程序的终止。而当寄宿在 Windows 服务下的时候,就可以通过服务管理器来启动和终止 Host 的执行。

其中,IHostLifetime 用来提供宿主 Host 在启动和停止时的通知机制。在 GitHub 中查看 IHostLifetime 的定义

using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Extensions.Hosting
{
    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>
        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>
        Task StopAsync(CancellationToken cancellationToken);
    }
}

该接口有多个实现,如果我们查看对于 Console 的实现,在 GitHub 中查看 ConsoleLifetime 源代码

    public class ConsoleLifetime : IHostLifetime, IDisposable
    {
        private readonly ManualResetEvent _shutdownBlock = new ManualResetEvent(false);
        private CancellationTokenRegistration _applicationStartedRegistration;
        private CancellationTokenRegistration _applicationStoppingRegistration;

        public ConsoleLifetime(IOptions<ConsoleLifetimeOptions> options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions<HostOptions> hostOptions)
            : this(options, environment, applicationLifetime, hostOptions, NullLoggerFactory.Instance) { }

        public ConsoleLifetime(IOptions<ConsoleLifetimeOptions> options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions<HostOptions> hostOptions, ILoggerFactory loggerFactory)
        {
            Options = options?.Value ?? throw new ArgumentNullException(nameof(options));
            Environment = environment ?? throw new ArgumentNullException(nameof(environment));
            ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
            HostOptions = hostOptions?.Value ?? throw new ArgumentNullException(nameof(hostOptions));
            Logger = loggerFactory.CreateLogger("Microsoft.Hosting.Lifetime");
        }

例如,ConsoleLifetime 的 WaitForStartAsync() 方法中,就定义了对 CancelKeyPress 的处理。

        public Task WaitForStartAsync(CancellationToken cancellationToken)
        {
            if (!Options.SuppressStatusMessages)
            {
                _applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state =>
                {
                    ((ConsoleLifetime)state).OnApplicationStarted();
                },
                this);
                _applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state =>
                {
                    ((ConsoleLifetime)state).OnApplicationStopping();
                },
                this);
            }

            AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
            Console.CancelKeyPress += OnCancelKeyPress;

            // Console applications start immediately.
            return Task.CompletedTask;
        }

当应用使用 Windows Service 的时候,则会使用基于 Windows 服务的实现,在 GitHub 中查看 WindowsServiceLifetime 的源代码

应用的生命周期

可以看到 ConsoleLifetime 的构造函数也通过依赖注入接受 IHostApplicationLifetime 对象实例。

这个 IHostApplicationLifetime 是什么呢?它负责应用程序本身的生命周期管理。它是独立于 Host 之外的。在 GitHub 中查看 IHostApplicationLifetime 源代码。它的定义非常简单。基于 CancellationToken,可以注册多个回调方法,可以在应用启动、停止前和停止后分别得到通知。

using System.Threading;

namespace Microsoft.Extensions.Hosting
{
    /// <summary>
    /// Allows consumers to be notified of application lifetime events. This interface is not intended to be user-replaceable.
    /// </summary>
    public interface IHostApplicationLifetime
    {
        /// <summary>
        /// Triggered when the application host has fully started.
        /// </summary>
        CancellationToken ApplicationStarted { get; }

        /// <summary>
        /// Triggered when the application host is starting a graceful shutdown.
        /// Shutdown will block until all callbacks registered on this token have completed.
        /// </summary>
        CancellationToken ApplicationStopping { get; }

        /// <summary>
        /// Triggered when the application host has completed a graceful shutdown.
        /// The application will not exit until all callbacks registered on this token have completed.
        /// </summary>
        CancellationToken ApplicationStopped { get; }

        /// <summary>
        /// Requests termination of the current application.
        /// </summary>
        void StopApplication();
    }
}

可以看到,还提供了一个 StopApplication() 可以关闭应用程序。

IHostApplicationLifetime 的实现类 ApplicationLifetime

ApplicationLifetime 实现了接口 IHostApplicationLifetime,并提供了一些辅助方法。

在 GitHub 中查看 ApplicationLifetime 源码

public class ApplicationLifetime : IApplicationLifetime, IHostApplicationLifetime {
    private readonly CancellationTokenSource _startedSource = new CancellationTokenSource();
    private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource();
    private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource();
    
    /// <summary>
    /// Triggered when the application host has fully started and is about to wait
    /// for a graceful shutdown.
    /// </summary>
    public CancellationToken ApplicationStarted => _startedSource.Token;

    /// <summary>
    /// Triggered when the application host is performing a graceful shutdown.
    /// Request may still be in flight. Shutdown will block until this event completes.
    /// </summary>
    public CancellationToken ApplicationStopping => _stoppingSource.Token;

    /// <summary>
    /// Triggered when the application host is performing a graceful shutdown.
    /// All requests should be complete at this point. Shutdown will block
    /// until this event completes.
    /// </summary>
    public CancellationToken ApplicationStopped => _stoppedSource.Token;
    
    public void StopApplication() {}
    public void NotifyStarted() {}
    public void NotifyStopped() {}
}

在实现中,这些方法用来发出生命周期的事件通知:

  • NotifyStarted()
  • NotifyStopped()

注册生命周期管理服务

这两个生命周期管理对象都是通过依赖注入注册到容器中。以后,通过依赖注入获取服务对象的时候,根据构造函数参数中的依赖描述,从容器中得到对象实例。

从下面的代码中,可以看到 IApplicationLifetime 实际上与 IHostApplicationLifetime 引用的都是同一个对象实例。

它们都是单例的。在 HostBuilder 中被注册到容器中。

在 GitHub 中查看 HostBuilder 源代码

private void CreateServiceProvider()
{
   var services = new ServiceCollection();
   #pragma warning disable CS0618 // Type or member is obsolete
   services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
   #pragma warning restore CS0618 // Type or member is obsolete
   services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
   services.AddSingleton(_hostBuilderContext);
   // register configuration as factory to make it dispose with the service provider
   services.AddSingleton(_ => _appConfiguration);

   #pragma warning disable CS0618 // Type or member is obsolete
   services.AddSingleton<IApplicationLifetime>(s => 
      (IApplicationLifetime)s.GetService<IHostApplicationLifetime>());
   #pragma warning restore CS0618 // Type or member is obsolete
   services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();
   services.AddSingleton<IHostLifetime, ConsoleLifetime>();
   services.AddSingleton<IHost, Internal.Host>();
   services.AddOptions();
   services.AddLogging();

启动服务 StartAsync

StartAsync 方法首先调用了 HostLifetime 的 WaitForStartAsync() 来注册 Host 本身的生命周期管理。

然后注册到 HostBuilder上下文中的 IHostedService 对象按照注册时的先后顺序执行IHostedService.StartAsync()方法进行启动。 将所有的IHostedService对象执行启动完毕后,通过应用程序生命周期管理对象IHostApplicationLifetime 通知应用程序,启动就完成了。

public IServiceProvider Services { get; }

public async Task StartAsync(CancellationToken cancellationToken = default)
{
    _logger.Starting();

    using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping);
    var combinedCancellationToken = combinedCancellationTokenSource.Token;

    await _hostLifetime.WaitForStartAsync(combinedCancellationToken);

    combinedCancellationToken.ThrowIfCancellationRequested();
    _hostedServices = Services.GetService<IEnumerable<IHostedService>>();

    foreach (var hostedService in _hostedServices)
    {
        // Fire IHostedService.Start
        await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
    }

    // Fire IHostApplicationLifetime.Started
    _applicationLifetime?.NotifyStarted();

    _logger.Started();
}

停止服务 StopAsync()

StopAsync 方法与 StartAsync 方法基本类似,首先通过应用程序生命周期管理对象 IHostApplicationLifetime 通知应用程序即将开始停止服务,然后一次调用IHostedService对象停止服务的运行,最后通知应用程序已完成结束方法。

public async Task StopAsync(CancellationToken cancellationToken = default)
{
    _logger.Stopping();

    using (var cts = new CancellationTokenSource(_options.ShutdownTimeout))
        using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
    {
        var token = linkedCts.Token;
        // Trigger IHostApplicationLifetime.ApplicationStopping
        _applicationLifetime?.StopApplication();

        IList<Exception> exceptions = new List<Exception>();
        if (_hostedServices != null) // Started?
        {
            foreach (var hostedService in _hostedServices.Reverse())
            {
                token.ThrowIfCancellationRequested();
                try
                {
                    await hostedService.StopAsync(token).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    exceptions.Add(ex);
                }
            }
        }

        token.ThrowIfCancellationRequested();
        await _hostLifetime.StopAsync(token);

        // Fire IHostApplicationLifetime.Stopped
        _applicationLifetime?.NotifyStopped();

        if (exceptions.Count > 0)
        {
            var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
            _logger.StoppedWithException(ex);
            throw ex;
        }
    }

    _logger.Stopped();
}

应用

这里面主要涉及到两个生命周期管理,其中最为主要的是 IHostApplicationLifetime,通过上面的分析,我们可以看到它已经被默认注册到依赖注入容器中,所以,我们可以在需要对应用的生命周期进行管理的时候,通过注入这个对象来获得对应用程序生命周期的响应。

posted on 2020-11-13 14:49  冠军  阅读(342)  评论(1编辑  收藏