想写好中间件,这是基础。

一、前言

今天这个内容,基于于ASP.NET Core 3.x。

从3.x开始,ASP.NET Core使用了通用主机模式。它将WebHostBuilder放到了通用的IHost之上,这样可以确保Kestrel可以运行在IHostedService中。

我们今天就来研究一下这个启动方式和启动顺序。

    为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/13636641.html

二、通常的启动次序

通常情况下,IHostedService的任何实现在添加到Startup.ConfigureServices()后,都会在GenericWebHostService之前启动。

这是微软官方给出的图。

这个图展示了在IHost上调用RunAsync()时的启动顺序(后者又调用StartAsync())。对我们来说,最重要的部分是启动的IHostedServices。从图上也可以看到,自定义IHostedServices先于GenericWebHostSevice启动。

我们来看一个简单的例子:

public class StartupHostedService : IHostedService
{
    private readonly ILogger _logger;
    public StartupHostedService(ILogger<StartupHostedService> logger)
    {
        _logger = logger;
    }
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Starting IHostedService registered in Startup");
        return Task.CompletedTask;
    }
    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Stopping IHostedService registered in Startup");
        return Task.CompletedTask;
    }
}

我们做一个简单的IHostedService。希望加到Startup.cs中:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHostedService<StartupHostedService>();
    }
}

运行代码:

info: demo.StartupHostedService[0]            # 这是上边的StartupHostedService
      Starting IHostedService registered in Startup
info: Microsoft.Hosting.Lifetime[0]            # 这是GenericWebHostSevice
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.

正如预期的那样,IHostedService首先执行,然后是GenericWebHostSeviceApplicationLifetime事件在所有IHostedServices执行之后触发。无论在什么地方注册了Startup.ConfigureServices()中的IHostedServiceGenericWebHostSevice都在最后启动。

那么问题来了,为什么GenericWebHostSevice在最后启动?

三、为什么`GenericWebHostSevice`在最后启动?

先看看多个IHostedService的情况。

当有多个IHostedService的实现加入到Startup.ConfigureServices()时,运行次序取决于它被加入的次序。

看例子:

public class Service1 : IHostedService
{
    private readonly ILogger _logger;
    public Service1(ILogger<Service1> logger)
    {
        _logger = logger;
    }
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Starting Service1");
        return Task.CompletedTask;
    }
    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Stoping Service1");
        return Task.CompletedTask;
    }
}
public class Service2 : IHostedService
{
    private readonly ILogger _logger;
    public Service2(ILogger<Service2> logger)
    {
        _logger = logger;
    }
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Starting Service2");
        return Task.CompletedTask;
    }
    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Stoping Service2");
        return Task.CompletedTask;
    }
}

Startup.cs:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHostedService<Service1>();
        services.AddHostedService<Service2>();
    }
}

运行:

info: demo.Service1[0]                # 这是Service1
      Starting Service1
info: demo.Service2[0]                # 这是Service2
      Starting Service2
info: Microsoft.Hosting.Lifetime[0]        # 这是GenericWebHostSevice
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.

那么,GenericWebHostSevice是什么时候注册的?

我们看看另一个文件Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>            # 这是GenericWebHostSevice注册的位置
            {
                webBuilder.UseStartup<Startup>();
            });
}

ConfigureWebHostDefaults扩展方法调用ConfigureWebHost方法,该方法执行Startup.ConfigureServices(),然后注册GenericWebHostService。整理一下代码,就是下面这个样子:

public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure)
{
    var webhostBuilder = new GenericWebHostBuilder(builder);     configure(webhostBuilder);     builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
    return builder;
}

这样可以确保GenericWebHostService总是最后运行,以保持通用主机实现和WebHost(已弃用)实现之间的行为一致。

因此,可以采用同样的方式,让IHostedServiceGenericWebHostService后面启动。

四、让`IHostedService`在`GenericWebHostService`后面启动

在大多数情况下,在GenericWebHostService之前启动IHostedServices就可以满足常规的应用。但是,GenericWebHostService还负责构建应用程序的中间件管道。如果IHostedService依赖于中间件管道或路由,那么就需要将它的启动延迟到GenericWebHostService完成之后。

根据上面的说明,在GenericWebHostService之后执行IHostedService的唯一方法是将它添加到GenericWebHostService之后的DI容器中。这意味着你必须跳出Startup.ConfigureServices(),在调用ConfigureWebHostDefaults之后,直接在IHostBuilder上调用ConfigureServices()

public class ProgramHostedService : IHostedService
{
    private readonly ILogger _logger;
    public ProgramHostedService(ILogger<ProgramHostedService> logger)
    {
        _logger = logger;
    }
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Starting ProgramHostedService registered in Program");
        return Task.CompletedTask;
    }
    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Stopping ProgramHostedService registered in Program");
        return Task.CompletedTask;
    }
}

加到Program.cs中:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }     public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>            # 这是GenericWebHostSevice注册的位置
            {
                webBuilder.UseStartup<Startup>();
            })
            .ConfigureServices(services => 
                services.AddHostedService<ProgramHostedService>());            # 这是ProgramHostedService注册的位置
}

看输出:

info: demo.StartupHostedService[0]            # 这是StartupHostedService
      Starting IHostedService registered in Startup
info: Microsoft.Hosting.Lifetime[0]            # 这是GenericWebHostSevice
      Now listening on: https://localhost:5001
info: demo.ProgramHostedService[0]            # 这是ProgramHostedService
      Starting ProgramHostedService registered in Program
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.

同样,在关闭应用时,IHostedServices被反向停止,所以ProgramHostedService首先停止,接着是GenericWebHostSevice,最后是StartupHostedService

info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
info: demo.ProgramHostedService[0]
      Stopping ProgramHostedService registered in Program
info: demo.StartupHostedService[0]
      Stopping IHostedService registered in Startup

五、总结

最后总结一下:

IHostedServices的执行顺序与它们在Startup.configureservices()中添加到DI容器中的顺序相同。运行侦听HTTP请求的Kestrel服务器的GenericWebHostSevice总是注册的IHostedServices之后运行。

要在GenericWebHostSevice之后启动IHostedService,需要在Program.cs中的IHostBuilder上ConfigureServices()扩展方法中进行注册。

(全文完)

本文的代码在:https://github.com/humornif/Demo-Code/tree/master/0024/demo


微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

ASP.NET Core 3.x控制IHostedService启动顺序浅探的更多相关文章

  1. 探索ASP.Net Core 3.0系列六:ASP.NET Core 3.0新特性启动信息中的结构化日志

    前言:在本文中,我将聊聊在ASP.NET Core 3.0中细小的变化——启动时记录消息的方式进行小的更改. 现在,ASP.NET Core不再将消息直接记录到控制台,而是正确使用了logging 基 ...

  2. 避免在ASP.NET Core 3.0中为启动类注入服务

    本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingE ...

  3. 【ASP.NET Core】运行原理之启动WebHost

    ASP.NET Core运行原理之启动WebHost 本节将分析WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build ...

  4. ASP.NET Core基础1:应用启动流程

    先看下ASP.NET Core的启动代码,如下图: 通过以上代码,我们可以初步得出以下结论: 所有的ASP.NET Core程序本质上也是一个控制台程序,使用Program的Main方法作为程序的入口 ...

  5. 【ASP.NET Core】运行原理(2):启动WebHost

    本系列将分析ASP.NET Core运行原理 [ASP.NET Core]运行原理[1]:创建WebHost [ASP.NET Core]运行原理[2]:启动WebHost [ASP.NET Core ...

  6. asp.net core 使用 AccessControlHelper 控制访问权限

    asp.net core 使用 AccessControlHelper 控制访问权限 Intro 由于项目需要,需要在基于 asp.net mvc 的 Web 项目框架中做权限的控制,于是才有了这个权 ...

  7. 在ASP.NET Core配置环境变量和启动设置

    在这一部分内容中,我们来讨论ASP.NET Core中的一个新功能:环境变量和启动设置,它将开发过程中的调试和测试变的更加简单.我们只需要简单的修改配置文件,就可以实现开发.预演.生产环境的切换. A ...

  8. ASP.NET Core配置环境变量和启动设置

    在这一部分内容中,我们来讨论ASP.NET Core中的一个新功能:环境变量和启动设置,它将开发过程中的调试和测试变的更加简单.我们只需要简单的修改配置文件,就可以实现开发.预演.生产环境的切换. A ...

  9. [转]ASP.NET Core配置环境变量和启动设置

    本文转自:https://www.cnblogs.com/tdfblog/p/Environments-LaunchSettings-in-Asp-Net-Core.html 在这一部分内容中,我们来 ...

随机推荐

  1. ECS7天实践进阶训练营Day1:使用阿里云ECS,快速搭建、管理VuePress静态网站

    一.概述 VuePress是2018年由尤雨溪发布的一个全新的基于Vue的静态网站生成器,它是一个非常轻量级的静态网站生成器.VuePress主要用于生成技术文档,其类似于Gitbook,我们可以用于 ...

  2. Kubeflow实战: 入门介绍与部署实践

    更多内容关注专辑: 机器学习实战 1 介绍 Kubeflow是在k8s平台之上针对机器学习的开发.训练.优化.部署.管理的工具集合,内部集成的方式融合机器学习中的很多领域的开源项目,比如Jupyter ...

  3. java Iterator迭代器

    一 Iterator迭代器概述 java中提供了很多个集合,它们在存储元素时,采用的存储方式不同.我们要取出这些集合 中的元素,可通过一种通用的获取方式来完成. Collection集合元素的通用获取 ...

  4. C#LeetCode刷题-线段树

    线段树篇 # 题名 刷题 通过率 难度 218 天际线问题   32.7% 困难 307 区域和检索 - 数组可修改   42.3% 中等 315 计算右侧小于当前元素的个数   31.9% 困难 4 ...

  5. JavaScript 循环数组的时候调用方法中包含Promise的时候如何做到串行

    forEach是不能阻塞的, 默认[并行]方式 const list = [1, 2, 3] const square = num => { return new Promise((resolv ...

  6. 解决用anaconda安装scrapy后,在使用scrapy时报错

    python版本为3.7 因为用anaconda安装scrapy非常方便,会自动下载所依赖的包, 所以就使用anaconda安装scrapy, 非常舒服,安装很成功 conda install scr ...

  7. Kubernetes实战指南(三十三):都0202了,你还在手写k8s的yaml文件?

    目录 1. k8s的yaml文件到底有多复杂 2. 基于图形化的方式自动生成yaml 2.1 k8s图形化管理工具Ratel安装 2.2 使用Ratel创建生成yaml文件 2.2.1 基本配置 2. ...

  8. Could not create an acl object: Role '16'

    解决方案: (1) 用rm命令一个一个的清除var/cache, var/page_cache, var/di, generated/文件夹下的所有文件 (2) run setup:upgradeco ...

  9. VyOS软路由系统基本设置

    1. VyOS简介 VyOS是一个开源的网络操作系统,可以安装在物理硬件上,也可以安装在你自己的虚拟机上,或者是一个云平台上.它基于GNU/Linux,并加入了多个应用程序,如:Quagga, ISC ...

  10. Nuxt.js 踩坑记录,(1)引入fs包报错

    今天又是码农的一天. 但是写着写着,不知道为啥就页面就报错了, 如图所示,我在db/app.js下引入了fs这个模块,提示我npm install,我也照做了,但是仍然报错. 通过各种百度,踩坑,最终 ...