• 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

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

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

  • 相关阅读:
    关于标准库中的ptr_fun/binary_function/bind1st/bind2nd
    ptr_fun学习笔记
    stream_iterator、ostream_iterator 使用初探
    C++异常处理
    ext2文件系统
    C语言动态内存管理
    C++中为什么要用虚函数、指针或引用才能实现多态?
    hadoop本地库
    二分图最小点覆盖König定理的简单证明 (加入自己理解)
    Fence Repair POJ
  • 原文地址:https://www.cnblogs.com/tiger-wang/p/13636641.html
Copyright © 2020-2023  润新知