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

掃描二維碼,關注我的公衆號,能夠第一時間獲得最新的我的文章和內容推送

本文版權歸做者全部,轉載請保留此聲明和原文連接

相關文章
相關標籤/搜索