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,通过上面的分析,我们可以看到它已经被默认注册到依赖注入容器中,所以,我们可以在需要对应用的生命周期进行管理的时候,通过注入这个对象来获得对应用程序生命周期的响应。

理解 ASP.NET Core: Host的更多相关文章

  1. 理解ASP.NET Core - [04] Host

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 本文会涉及部分 Host 相关的源码,并会附上 github 源码地址,不过为了降低篇幅,我会 ...

  2. 理解ASP.NET Core - [01] Startup

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 准备工作:一份ASP.NET Core Web API应用程序 当我们来到一个陌生的环境,第一 ...

  3. 目录-理解ASP.NET Core

    <理解ASP.NET Core>基于.NET5进行整理,旨在帮助大家能够对ASP.NET Core框架有一个清晰的认识. 目录 [01] Startup [02] Middleware [ ...

  4. 理解ASP.NET Core - [03] Dependency Injection

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 依赖注入 什么是依赖注入 简单说,就是将对象的创建和销毁工作交给DI容器来进行,调用方只需要接 ...

  5. 理解ASP.NET Core - 配置(Configuration)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 配置提供程序 在.NET中,配置是通过多种配置提供程序来提供的,包括以下几种: 文件配置提供程 ...

  6. 理解ASP.NET Core - 文件服务器(File Server)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 提供静态文件 静态文件默认存放在 Web根目录(Web Root) 中,路径为 项目根目录(C ...

  7. 理解ASP.NET Core - 日志(Logging)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 快速上手 添加日志提供程序 在文章主机(Host)中,讲到Host.CreateDefault ...

  8. 理解 ASP.NET Core: 处理管道

    理解 ASP.NET Core 处理管道 在 ASP.NET Core 的管道处理部分,实现思想已经不是传统的面向对象模式,而是切换到了函数式编程模式.这导致代码的逻辑大大简化,但是,对于熟悉面向对象 ...

  9. 理解ASP.NET Core - [02] Middleware

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 中间件 先借用微软官方文档的一张图: 可以看到,中间件实际上是一种配置在HTTP请求管道中,用 ...

  10. 理解ASP.NET Core - 选项(Options)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 Options绑定 上期我们已经聊过了配置(IConfiguration),今天我们来聊一聊O ...

随机推荐

  1. 如何判断一个网站是用的Nginx,还是Apache

    事件起因: 接手了同事移交过来的一个网站,但是不知道这个网站是用什么做代理的,于是就去网上查资料   解决办法: 打开cmd窗口,输入以下命令即可 curl --head 域名/IP 注意,--hea ...

  2. Flutter TextField 的高度问题

    示例 先来看一个例子:假设我们要做一个表单,左边是提示文字,右边是输入框 给出代码: Row( crossAxisAlignment: CrossAxisAlignment.center, child ...

  3. laravel中添加公共函数

    laravel中添加公共函数 1. 在项目中的新建app/Helper/functions.php文件 2.在项目的跟目录找到composer.json 文件,并打开,然后再autoload中添加如下 ...

  4. OOOPS:零样本实现360度开放全景分割,已开源 | ECCV'24

    全景图像捕捉360°的视场(FoV),包含了对场景理解至关重要的全向空间信息.然而,获取足够的训练用密集标注全景图不仅成本高昂,而且在封闭词汇设置下训练模型时也受到应用限制.为了解决这个问题,论文定义 ...

  5. kotlin协程——>异常处理

    异常处理 本节内容涵盖了异常处理与在异常上取消.我们已经知道取消协程会在挂起点抛出 CancellationException 并且它会被协程的机制所忽略.在这⾥我们会看看在取消过程中抛出异常或同 ⼀ ...

  6. Python 潮流周刊#72:Python 3.13.0 最终版已发布!(摘要)

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  7. 云原生周刊:K8s 1.26 到 1.29 版本的更新 | 2024.1.29

    开源项目推荐 Skaffold Skaffold 是一个命令行工具,有助于 Kubernetes 应用程序的持续开发.您可以在本地迭代应用程序源代码,然后部署到本地或远程 Kubernetes 集群. ...

  8. 云原生爱好者周刊:开源替代品开始围剿 Docker Desktop

    云原生一周动态要闻: Docker 更新和扩展了产品订阅 NGINX Ingress Controller 1.0.0 发布 Tanzu 应用平台的公开测试版发布 IBM 开源 Tornjak Kub ...

  9. 报名开启|QKE 容器引擎托管版暨容器生态发布会!

    当下,"云原生"技术红利正吞噬旧秩序,重塑新世界. 但您的企业是否依然困惑:缺少运维人员或运维团队,想要专注于业务的开发,又不得不兼顾集群的日常运维:在生产环境中,为了保证业务的高 ...

  10. manim边做边学--复数平面

    所谓复数平面,就是一种二维坐标系统,用于几何表示复数的场景,其中横轴代表实部,纵轴代表虚部. 每个点对应一个唯一的复数,反之亦然,这种表示方法使得复数的加法.乘法等运算可以通过直观的图形变换来理解. ...