• ASP.NET Core 3.x控制IHostedService启动顺序浅探


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

    一、前言

    今天这个内容,基于于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

    infoMicrosoft.Hosting.Lifetime[0]
          Application is shutting down...
    infodemo.ProgramHostedService[0]
          Stopping ProgramHostedService registered in Program
    infodemo.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

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

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

  • 相关阅读:
    网络安全分析
    java实现 洛谷 P1464 Function
    java实现 洛谷 P1464 Function
    java实现 洛谷 P1014 Cantor表
    java实现 洛谷 P1014 Cantor表
    java实现 洛谷 P1014 Cantor表
    java实现 洛谷 P1014 Cantor表
    java实现 洛谷 P1014 Cantor表
    java实现 洛谷 P1540 机器
    java实现 洛谷 P1540 机器
  • 原文地址:https://www.cnblogs.com/tiger-wang/p/13636641.html
Copyright © 2020-2023  润新知