<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ASP.NET Core 3.x啟動時運行異步任務(二)

          共 4797字,需瀏覽 10分鐘

           ·

          2020-09-26 05:30

          這一篇是接著前一篇在寫的。如果沒有看過前一篇文章,建議先去看一下前一篇,這兒是傳送門

          ?

          一、前言

          前一篇文章,我們從應用啟動時異步運行任務開始,說到了必要性,也說到了幾種解決方法,及各自的優(yōu)缺點。最后,還提出了一個比較合理的解決方法:通過在Program.cs里加入代碼,來實現(xiàn)IWebHost啟動前運行異步任務。

          實現(xiàn)的代碼再貼一下:

          public?class?Program
          {

          ????public?static?async?Task?Main(string[]?args)
          ????
          {
          ????????IWebHost?webHost?=?CreateWebHostBuilder(args).Build();

          ????????using?(var?scope?=?webHost.Services.CreateScope())
          ????????{
          ????????????var?myDbContext?=?scope.ServiceProvider.GetRequiredService();

          ????????????await?myDbContext.Database.MigrateAsync();
          ????????}

          ????????await?webHost.RunAsync();
          ????}

          ????public?static?IWebHostBuilder?CreateWebHostBuilder(string[]?args)?=>
          ????????WebHost.CreateDefaultBuilder(args)
          ????????????.UseStartup();
          }

          這個方法是有效的。但是,也會有一點不足。

          從.Net Core的最簡規(guī)則來說,我們不應該在Program.cs中加入其它代碼。當然,我們可以把這部分代碼轉到一個外部類中,但最后也必須手動加入到Program.cs中。尤其是在多個應用中,使用相同的模式時,這種方式會很麻煩。

          也許,我們可以采用向DI容器中注入啟動任務?

          二、向DI容器中注入啟動任務

          這種方式,是基于IStartupFilterIHostedService兩個接口,通過這兩個接口可以向依賴注入容器中注冊類。

          ?

          首先,我們?yōu)閱尤蝿談?chuàng)建一個簡單接口:

          public?interface?IStartupTask
          {
          ????Task?ExecuteAsync(CancellationToken?cancellationToken?=?default);
          }

          再建一個擴展方法,用來向DI容器注冊啟動任務:

          public?static?class?ServiceCollectionExtensions
          {

          ????public?static?IServiceCollection?AddStartupTask(this?IServiceCollection?services)
          ????????where?T?:?class,?IStartupTask
          ????????=>?services.AddTransient();

          }

          最后,再建一個擴展方法,在應用啟動時,查找所有已注冊的IStartupTask,按順序執(zhí)行他們,然后啟動IWebHost

          public?static?class?StartupTaskWebHostExtensions
          {

          ????public?static?async?Task?RunWithTasksAsync(this?IHost?webHost,?CancellationToken?cancellationToken?=?default)
          ????
          {
          ????????var?startupTasks?=?webHost.Services.GetServices();

          ????????foreach?(var?startupTask?in?startupTasks)
          ????????{
          ????????????await?startupTask.ExecuteAsync(cancellationToken);
          ????????}

          ????????await?webHost.RunAsync(cancellationToken);
          ????}
          }

          這樣就齊活了。

          ?

          還是用一個例子來看看這個方式的具體應用。

          三、示例 - 數據遷移

          實現(xiàn)IStartupTask其實和實現(xiàn)IStartupFilter很相似,可以從DI容器中注入。如果需要考慮作用域,還可以注入IServiceProvider,并手動創(chuàng)建作用域。

          ?

          例子中,數據遷移類可以寫成這樣:

          public?class?MigratorStartupFilter:?IStartupTask
          {
          ????private?readonly?IServiceProvider?_serviceProvider;
          ????public?MigratorStartupFilter(IServiceProvider?serviceProvider)
          ????
          {
          ????????_serviceProvider?=?serviceProvider;
          ????}

          ????public?async?Task?ExecuteAsync(CancellationToken?cancellationToken?=?default)
          ????
          {
          ????????using(var?scope?=?_seviceProvider.CreateScope())
          ????????{
          ????????????var?myDbContext?=?scope.ServiceProvider.GetRequiredService();
          ????????????await?myDbContext.Database.MigrateAsync();
          ????????}
          ????}
          }

          下面,把任務注入到ConfigureServices()中:

          public?void?ConfigureServices(IServiceCollection?services)
          {
          ????services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

          ????services.AddStartupTask();
          }

          最后,用上一節(jié)中的擴展方法RunWithTasksAsync()來替代Program.cs中的Run():

          public?class?Program
          {

          ????public?static?async?Task?Main(string[]?args)
          ????
          {
          ????????//?await?CreateWebHostBuilder(args).Build().RunAsync();
          ????????await?CreateWebHostBuilder(args).Build().RunWithTasksAsync();
          ????}

          ????public?static?IWebHostBuilder?CreateWebHostBuilder(string[]?args)?=>
          ????????WebHost.CreateDefaultBuilder(args)
          ????????????.UseStartup();
          }

          ?

          從功能上來說,跟上一篇的代碼區(qū)別不大,但這樣的寫法,又多了一些優(yōu)點:

          1. 任務代碼放到了Program.cs之外。這符合微軟的建議,也更容易理解;

          2. 任務放到了DI容器中,這樣更容易添加額外的任務;

          3. 如果沒有額外任務,這個代碼和標準的Run()一樣,所以這個代碼可以獨立成一個模板。

          簡單來說,使用RunWithTasksAsync()后,可以輕松地向DI容器添加額外的任務,而不需要任何其它的更改。

          ?

          滿意了嗎?好像感覺還差一點點…

          四、不夠完美的地方

          如果要照著完美去做,好像還差一點點。

          這個一點點是在于:任務現(xiàn)在運行在IConfiguration和DI容器配置完成后,IStartupFilters運行和中間件管道配置完成之前。換句話說,如果任務需要依賴于IStartupFilters,那這個方案行不通。

          在大多數情況下,這沒什么問題。以我自己的經驗來看,好像沒有什么功能需要依賴于IStartupFilters。但作為一個框架類的代碼,需要考慮這種情況發(fā)生的可能性。

          以目前的方案來說,好像還沒辦法解決。

          應用啟動時,當調用WebHost.Run()時,是內部調用WebHost??匆幌?code style="font-size: inherit;line-height: inherit;word-wrap: break-word;padding: 2px 4px;border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background-color: rgb(248, 248, 248);">StartAsync()的簡化代碼:

          public?virtual?async?Task?StartAsync(CancellationToken?cancellationToken?=?default)
          {
          ????_logger?=?_applicationServices.GetRequiredService>();

          ????var?application?=?BuildApplication();

          ????_applicationLifetime?=?_applicationServices.GetRequiredService()?as?ApplicationLifetime;
          ????_hostedServiceExecutor?=?_applicationServices.GetRequiredService();
          ????var?diagnosticSource?=?_applicationServices.GetRequiredService();
          ????var?httpContextFactory?=?_applicationServices.GetRequiredService();
          ????var?hostingApp?=?new?HostingApplication(application,?_logger,?diagnosticSource,?httpContextFactory);

          ????await?Server.StartAsync(hostingApp,?cancellationToken).ConfigureAwait(false);

          ????_applicationLifetime?.NotifyStarted();

          ????await?_hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
          }

          如果我們希望任務是加在BuildApplication()調用和Server.StartAsync()的調用之間,該怎么辦?

          這段代碼能給出答案:我們需要裝飾IServer?!16K 首先,我們替換IServer的實現(xiàn):¨G8G 在這段代碼中,我們攔截StartAsync()調用并注入任務,然后回到內置處理。下面是對應的擴展代碼:¨G9G 這個擴展代碼做了兩件事:在DI容器中注冊了IStartupTask,并裝飾了之前注冊的IServer實例。裝飾方法Decorate()我略過了,有興趣的可以去了解一下 - 裝飾模式。?Program.cs的代碼和第三節(jié)的代碼相同,略過。? 我們終于做到了在應用程序完全構建完成后去執(zhí)行我們的任務,包括IStartupFilters`和中間件管道。

          現(xiàn)在的流程,類似于下面這個微軟官方的圖:

          (全文完)


          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  曰韩欧美一级 | 五月天婷婷综合网 | 可以免费观看的黄色视屏 | 中国女人性爱毛片 | 青青草成人A视频 |