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

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

1、前言

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

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

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

    为了防止不提供原网址的转载,特在这里加上原文连接:http://www.javashuo.com/article/p-bficxesq-ng.htmlweb

2、一般的启动次序

一般状况下,IHostedService的任何实如今添加到Startup.ConfigureServices()后,都会在GenericWebHostService以前启动。c#

这是微软官方给出的图。服务器

这个图展现了在IHost上调用RunAsync()时的启动顺序(后者又调用StartAsync())。对咱们来讲,最重要的部分是启动的IHostedServices。从图上也能够看到,自定义IHostedServices先于GenericWebHostSevice启动。微信

咱们来看一个简单的例子:app

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中:ui

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在最后启动?

3、为何`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后面启动。

4、让`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

5、总结

最后总结一下:

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

扫描二维码,关注我的公众号,能够第一时间获得最新的我的文章和内容推送

本文版权归做者全部,转载请保留此声明和原文连接

相关文章
相关标签/搜索