<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啟動(dòng)時(shí)運(yùn)行異步任務(wù)(一)

          共 5849字,需瀏覽 12分鐘

           ·

          2020-09-26 05:30

          這是一個(gè)大的題目,需要用幾篇文章來(lái)說(shuō)清楚。這是第一篇。

          ?

          一、前言

          在我們的項(xiàng)目中,有時(shí)候我們需要在應(yīng)用程序啟動(dòng)前執(zhí)行一些一次性的邏輯。比方說(shuō):驗(yàn)證配置的正確性、填充緩存、或者運(yùn)行數(shù)據(jù)庫(kù)清理/遷移等。

          如何合理、有效、優(yōu)雅地完成這個(gè)任務(wù),是這個(gè)文章討論的主要內(nèi)容。

          ?

          要實(shí)現(xiàn)這樣一個(gè)功能,其實(shí)我們有幾個(gè)選擇:

          1. 使用IStartupFilter運(yùn)行同步任務(wù)。這是一個(gè)內(nèi)置的解決方案,可以通過(guò)一些設(shè)置和技巧來(lái)運(yùn)行異步任務(wù);

          2. 使用IStartupFilterIApplicationLifetime事件來(lái)運(yùn)行異步任務(wù),這是一個(gè)可選的方案,但有不足,我們會(huì)在后面講;

          3. 使用IHostedService,在不阻塞應(yīng)用啟動(dòng)的情況下,運(yùn)行一些一次性的任務(wù);(關(guān)于這個(gè)內(nèi)容,我在前一篇文章ASP.NET Core 3.x控制IHostedService啟動(dòng)順序淺探中有涉及到一部分內(nèi)容)

          4. Program.cs中運(yùn)行異步任務(wù)。在大多數(shù)情況下,從代碼的復(fù)雜度到效率上,這都是一個(gè)比較好的選擇。

          ?

          先提個(gè)問(wèn)題:為什么要在應(yīng)用啟動(dòng)時(shí)運(yùn)行任務(wù)?

          二、為什么要在應(yīng)用啟動(dòng)時(shí)運(yùn)行任務(wù)?

          在應(yīng)用啟動(dòng)并開(kāi)始請(qǐng)求服務(wù)之前,很多時(shí)候需要運(yùn)行各種初始化工作。

          一個(gè)ASP.NET應(yīng)用啟動(dòng)時(shí),需要完成很多事,例如:

          • 確定當(dāng)前的宿主環(huán)境

          • 加載appsetting.json配置和環(huán)境變量

          • 配置并創(chuàng)建依賴注入的容器

          • 配置中間件管道

          這是應(yīng)用啟動(dòng)時(shí)要完成的引導(dǎo)內(nèi)容。

          在完成這些內(nèi)容,運(yùn)行WebHost并開(kāi)始監(jiān)聽(tīng)請(qǐng)求之前,還會(huì)有一些一次性任務(wù)需要啟動(dòng),例如:

          • 檢查強(qiáng)類型配置的有效性

          • 填充或恢復(fù)緩存

          • 數(shù)據(jù)庫(kù)清理/遷移(通常來(lái)說(shuō)這不是個(gè)好主意,但很多時(shí)候沒(méi)有別的辦法)

          當(dāng)然,有些任務(wù)也不是一定要在開(kāi)始監(jiān)聽(tīng)請(qǐng)求之前運(yùn)行,這要看具體的運(yùn)行任務(wù)的架構(gòu)。一般來(lái)說(shuō),如果緩存處理的完善,是不需要提前啟動(dòng)的。當(dāng)然,清理/遷移數(shù)據(jù)庫(kù),是必須放在服務(wù)啟動(dòng)之前。

          在微軟官網(wǎng)上,有一個(gè)例子是數(shù)據(jù)保護(hù)子系統(tǒng),用于即時(shí)加密(cookie、防偽令牌等),這個(gè)就必須在應(yīng)用監(jiān)聽(tīng)請(qǐng)求之前完成初始化并加載,這個(gè)例子使用了IStartupFilter。

          三、使用IStartupFilter運(yùn)行同步任務(wù)

          IStartupFilters作為配置中間件管道的一部分,通常在Startup.Configure()中運(yùn)行。它允許我們定制應(yīng)用的中間件管道,處理我們希望進(jìn)行的所有任務(wù)。

          看一個(gè)簡(jiǎn)單的例子:

          public?class?AutoRequestServicesStartupFilter?:?IStartupFilter
          {
          ????public?Action?Configure(Action?next)
          ????{
          ????????return?builder?=>
          ????????{
          ????????????builder.UseMiddleware();
          ????????????next(builder);
          ????????};
          ????}
          }

          IStartupFilter提供了一種可能,在依賴注入容器配置完成之后、應(yīng)用程序啟動(dòng)之前運(yùn)行一些代碼。因此,我們可以在IStartupFilters中直接使用依賴注入。這表示我們可以運(yùn)行有關(guān)系統(tǒng)的任何代碼。在前邊提到的微軟官網(wǎng)的例子中,就是創(chuàng)建了一個(gè)基于IStartupFiltersDataProtectionStartupFilter來(lái)初始化數(shù)據(jù)保護(hù)子系統(tǒng)。

          此外,IStartupFilter允許我們通過(guò)向依賴注入容器注冊(cè)服務(wù)來(lái)增加要執(zhí)行的任務(wù)。這是一個(gè)很有用的特性,表示我們可以注冊(cè)一個(gè)在應(yīng)用啟動(dòng)時(shí)運(yùn)行的任務(wù),而不需要顯式的調(diào)用。

          但是,這兒有個(gè)問(wèn)題。IStartupFilters通常運(yùn)行的是同步的任務(wù)??匆幌律厦娴拇a,Configure()方法不返回任務(wù)。當(dāng)然,我們硬要使用異步也是可以的,但一般來(lái)說(shuō),這不算個(gè)好主意。原因我后面會(huì)寫(xiě)。

          ?

          寫(xiě)到這兒,如果對(duì)ASP.NET Core架構(gòu)熟悉,就會(huì)引出另一個(gè)問(wèn)題:為什么不用健康檢查來(lái)確認(rèn)一次性任務(wù)的執(zhí)行結(jié)果?

          四、為什么不用健康檢查?

          運(yùn)行健康檢查,是ASP.NET Core 2.2新引入的一個(gè)特性,允許查詢通過(guò)API(HTTP Endpoint)公開(kāi)的應(yīng)用的健康狀況。當(dāng)應(yīng)用部署在Kubernetes,或反向代理HAProxyNginx后面時(shí),可以提供給代理用來(lái)檢測(cè)應(yīng)用是否準(zhǔn)備好開(kāi)始提供服務(wù)。

          我們可以使用健康檢查來(lái)確保應(yīng)用所有必需的一次性任務(wù)完成之前不會(huì)開(kāi)始監(jiān)聽(tīng)服務(wù)。

          但是,這種方式會(huì)有一點(diǎn)問(wèn)題。

          WebHostKestrel本身會(huì)在一次性任務(wù)執(zhí)行前啟動(dòng)。當(dāng)然,這時(shí)他們還不會(huì)接收和處理服務(wù)請(qǐng)求,但仍然引出了一些問(wèn)題:

          首先是增加了代碼的復(fù)雜性。除了一次性任務(wù)的代碼外,還要增加健康檢查來(lái)測(cè)試任務(wù)是否完成,并同步和保持任務(wù)的狀態(tài);其次,如果任務(wù)失敗了,應(yīng)用程序的健康檢查將會(huì)讓?xiě)?yīng)用后續(xù)的任務(wù)無(wú)法繼續(xù)執(zhí)行。合理的流程是:應(yīng)用應(yīng)該立即失敗返回。

          這兒主要的原因是:健康檢查沒(méi)有定義如何實(shí)際運(yùn)行任務(wù),而只是定義了任務(wù)是否成功完成。相對(duì)來(lái)說(shuō),這種狀態(tài)機(jī)制比較單一,在一些簡(jiǎn)單的任務(wù)中可能適用,但不能全面覆蓋一次性任務(wù)的全部場(chǎng)景。

          五、運(yùn)行異步任務(wù)

          前邊寫(xiě)了一些不太完美的方法。

          現(xiàn)在,我們開(kāi)始進(jìn)入運(yùn)行異步方法的一些步驟。當(dāng)然,運(yùn)行異步也會(huì)有幾種方式,適用性上會(huì)有一定的區(qū)別。

          方式1:使用IStartupFilter

          前邊說(shuō)過(guò),使用IStartupFilter時(shí),執(zhí)行的是同步任務(wù)。所以,我們可以通過(guò)GetAwater().GetResult()來(lái)調(diào)用異步。

          ?

          我們拿數(shù)據(jù)遷移來(lái)舉個(gè)例子。在EF Core中,通過(guò)myDBContext.database.migrateasync()在運(yùn)行時(shí)進(jìn)行數(shù)據(jù)庫(kù)遷移。其中,myDBContext是應(yīng)用程序中DBContext的一個(gè)實(shí)例。

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

          ????public?Action?Configure(Action?next)
          ????{
          ????????using(var?scope?=?_seviceProvider.CreateScope())
          ????????{
          ????????????var?myDbContext?=?scope.ServiceProvider.GetRequiredService();

          ????????????myDbContext.Database.MigrateAsync()
          ????????????????.GetAwaiter()
          ????????????????.GetResult();
          ????????}

          ????????return?next;
          ????}
          }

          通常,GetAwaiter().GetResult()要注意避免死鎖的問(wèn)題。但這兒可能不需要,因?yàn)檫@個(gè)代碼只在啟動(dòng)時(shí)運(yùn)行,這時(shí)候還沒(méi)有需要處理的請(qǐng)求,所以不太會(huì)死鎖。

          只能說(shuō),這樣可以用。不過(guò)習(xí)慣上我會(huì)避免這么做。

          方式2:使用IApplicationLifetime事件

          這是另一個(gè)選擇。可以通過(guò)IApplicationLifetime事件,在應(yīng)用啟動(dòng)和關(guān)閉時(shí)接收通知,處理任務(wù)。

          但這個(gè)方式也有局限性。

          首先,IApplicationLifetime使用cancellationtoken來(lái)注冊(cè)回調(diào),也就是說(shuō),這又是一個(gè)同步方式,又需要使用GetAwaiter().GetResult()來(lái)調(diào)用異步。

          其次,ApplicationStarted事件是在WebHost啟動(dòng)之后才會(huì)觸發(fā),因此異步任務(wù)也是在應(yīng)用開(kāi)始監(jiān)聽(tīng)請(qǐng)求后才運(yùn)行。

          方式3:使用IHostedService

          IHostedService可以讓ASP.NET Core應(yīng)用在后臺(tái)執(zhí)行長(zhǎng)時(shí)間的任務(wù)。

          一般來(lái)說(shuō),IHostedService用在周期性任務(wù)、消息傳遞等任務(wù)上,但實(shí)際上它并不限于運(yùn)行這些任務(wù)。在ASP.NET Core 3.x上,WebHost本身也是建立在IHostedService上的。

          而且,IHostedService本身就是異步的,它提供了StartAsyncStopAsync。

          這種方式下,我們的代碼會(huì)是這樣:

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

          ????public?async?Task?StartAsync(CancellationToken?cancellationToken)
          ????
          {
          ????????using(var?scope?=?_seviceProvider.CreateScope())
          ????????{
          ????????????var?myDbContext?=?scope.ServiceProvider.GetRequiredService();

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

          ????public?Task?StopAsync(CancellationToken?cancellationToken)
          ????
          {
          ????????return?Task.CompletedTask;
          ????}
          }

          根據(jù)例子可以看出,IHostedService可以直接運(yùn)行異步任務(wù)。

          但是,IHostedService也有局限性。從微軟官網(wǎng)的說(shuō)明來(lái)看,IHostedService實(shí)現(xiàn)期望StartAsync能相對(duì)較快的返回。對(duì)于后臺(tái)任務(wù),傾向于異步啟動(dòng),但主要任務(wù)在啟動(dòng)后執(zhí)行。

          在上面這個(gè)例子中,數(shù)據(jù)遷移本身不是問(wèn)題,但這個(gè)長(zhǎng)時(shí)任務(wù)會(huì)阻止其它`IHostedService啟動(dòng)和運(yùn)行。而且,應(yīng)用會(huì)在IHostedService完成數(shù)據(jù)遷移前開(kāi)始監(jiān)聽(tīng)并響應(yīng)請(qǐng)求,這是一個(gè)嚴(yán)重的問(wèn)題。

          方式4:在Program.cs中運(yùn)行

          上面三個(gè)方式,都可以解決啟動(dòng)時(shí)運(yùn)行異步任務(wù)的問(wèn)題,但都不夠完美,要么要求使用同步(異步轉(zhuǎn)同步可以用,但有隱藏問(wèn)題),要么不能阻止應(yīng)用啟動(dòng),會(huì)造成應(yīng)用啟動(dòng)完成后,可能異步任務(wù)還未完成的情況。

          我在前邊的博文中寫(xiě)到過(guò)關(guān)于Program.cs中運(yùn)行IHostedService的方式。具體可以去看ASP.NET Core 3.x控制IHostedService啟動(dòng)順序淺探

          看一下Program.cs的默認(rèn)代碼:

          public?class?Program
          {

          ????public?static?void?Main(string[]?args)
          ????
          {
          ????????CreateWebHostBuilder(args).Build().Run();
          ????}

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

          Build()創(chuàng)建WebHost之后,調(diào)用Run()之前,完全可以加入我們需要的代碼。同時(shí),C# 7.1后主函數(shù)可以改為異步運(yù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();
          }

          這個(gè)方案的好處是:

          • 這是真正的異步;

          • 任務(wù)完成后,應(yīng)用程序才可以監(jiān)聽(tīng)并接受請(qǐng)求;

          • 此時(shí)已經(jīng)構(gòu)建了依賴注入容器,所以可以創(chuàng)建服務(wù);

          當(dāng)然,同樣也會(huì)有不足:這兒只是構(gòu)建了DI容器,但并沒(méi)有建立管道(管道在Run()、RunAsync()后才建立,然后是IStartupFilters執(zhí)行,再然后是應(yīng)用程序啟動(dòng))。因此異步任務(wù)不能使用管道、IStartupFilters中的配置。不過(guò),這種需求的情況很少。

          六、總結(jié)

          這個(gè)部分牽扯到的框架內(nèi)容比較多。

          我們從應(yīng)用啟動(dòng)時(shí)異步運(yùn)行任務(wù)開(kāi)始,說(shuō)到了必要性,也說(shuō)到了幾種解決方法,及各自的優(yōu)缺點(diǎn)。

          下一篇文章,我會(huì)用一些具體的例子,來(lái)說(shuō)清楚這個(gè)方式的具體使用,敬請(qǐng)關(guān)注。

          (未完待續(xù))


          瀏覽 77
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  波多野成人无码精品视频 | 婷婷丁香五月天在线视频 | 色情小电影免费网站观看网址在线播 | 国产精品成人视频 | 欧美日韩777 |