<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>

          .NET 異步,你也許不知道的5種用法

          共 7233字,需瀏覽 15分鐘

           ·

          2021-04-01 17:30


          async/await異步操作,是C#中非常驚艷的“語法糖”,讓異步編程變得優(yōu)美且傻瓜化到了不可思議的程度。就連JavaScript都借鑒了async/await語法,讓回調(diào)泛濫的JavaScript代碼變得很優(yōu)美。

          我之前錄制的.NET視頻教程已經(jīng)把a(bǔ)sync/await等基礎(chǔ)知識介紹了,這篇文章不再介紹那些基礎(chǔ)知識,如果有對它們還不了解的朋友,請到我的B站、頭條、油管等平臺搜索“楊中科 .net 教程”查看。

          本篇文章只對在之前的視頻教程中沒有提到的幾點做講解。

           

          用法1、控制并行執(zhí)行的任務(wù)數(shù)量

                 在項目開發(fā)的時候,有時候有很多任務(wù)需要異步執(zhí)行,但是為了避免同時執(zhí)行的異步任務(wù)太多,反而降低性能,因此通常需要限制并行執(zhí)行的任務(wù)的數(shù)量。比如爬蟲并行從網(wǎng)上抓取內(nèi)容的時候,就要根據(jù)情況限制最大執(zhí)行的線程的數(shù)量。

          在沒有async/await的年代,需要使用信號量等機(jī)制來進(jìn)行線程間通訊來協(xié)調(diào)各個線程的執(zhí)行,需要開發(fā)者對于多線程的技術(shù)細(xì)節(jié)非常了解。而使用async/await之后,這一切就可以變得非常傻瓜化了。

          比如下面的代碼用來首先從words.txt這個每行一個英文單詞的字典中,逐個讀取單詞,然后調(diào)用一個API接口來獲得單詞的“音標(biāo)、中文含義、例句”等詳細(xì)信息。為了加快處理速度,需要采用異步編程來實現(xiàn)多任務(wù)同時下載,但是又要限制同時執(zhí)行的任務(wù)的數(shù)量(假設(shè)為5個)。實現(xiàn)代碼如下:

          class Program{       static async Task Main(string[] args)       {              ServiceCollectionservices = new ServiceCollection();              services.AddHttpClient();              services.AddScoped<WordProcessor>();              using(var sp = services.BuildServiceProvider())              {                     var wp = sp.GetRequiredService<WordProcessor>();                     string[]words = await File.ReadAllLinesAsync("d:/temp/words.txt");                     List<Task>tasks = new List<Task>();                     foreach(var word in words)                     {                            tasks.Add(wp.ProcessAsync(word));                            if(tasks.Count==5)                            {                                   //waitwhen five tasks are ready                                   awai tTask.WhenAll(tasks);                                   tasks.Clear();                            }                     }                     //waitthe remnant which are less than five.                     await Task.WhenAll(tasks);              }              Console.WriteLine("done!");       }} class WordProcessor{       private IHttpClientFactory httpClientFactory;       public WordProcessor(IHttpClientFactory httpClientFactory)       {              this.httpClientFactory= httpClientFactory;       }        publicasync Task ProcessAsync(string word)       {              Console.WriteLine(word);              var httpClient = this.httpClientFactory.CreateClient();              string json = await httpClient.GetStringAsync("http://dict.e.opac.vip/dict.php?sw="+ Uri.EscapeDataString(word));              await File.WriteAllTextAsync("d:/temp/words/" + word + ".txt",json);       }}


           

          核心代碼就是下面這一段:

          List<Task> tasks = newList<Task>();foreach(var word in words){       tasks.Add(wp.ProcessAsync(word));       if(tasks.Count==5)       {              //waitwhen five tasks are ready              await Task.WhenAll(tasks);              tasks.Clear();       }}


          這里遍歷所有單詞,抓取單詞并且保存到磁盤的Process方法的返回值Task沒有使用await關(guān)鍵字進(jìn)行修飾,而是把返回的Task對象保存到list中,由于沒有使用await進(jìn)行等待,因此不用等一個任務(wù)執(zhí)行完成,就可以把下一個任務(wù)加入list。當(dāng)list中的任務(wù)滿五個的時候,就調(diào)用await Task.WhenAll(tasks);等待這五個任務(wù)執(zhí)行完成后,再處理下一組(5個)。循環(huán)之外的await Task.WhenAll(tasks);的是用來處理最后一組不足5個任務(wù)的情況。

           

          用法2、在BackgroundService等異步執(zhí)行的代碼中進(jìn)行DI注入

           

              依賴注入(DI)的時候,注入的對象都是有生命周期的。比如使用services.AddDbContext<TestDbContext>(...);這種方式注入EF Core中的DbContext的時候,TestDbContext的生命周期就是Scope。在普通的MVC的Controller中可以直接注入TestDbContext,但是在BackgroundService中是不能直接注入TestDbContext的。這時候,可以注入IServiceScopeFactory對象,然后在使用到TestDbContext對象的時候再調(diào)用IServiceScopeFactory的CreateScope()方法來生成一個IServiceScope,并且使用IServiceScope的ServiceProvider來手動解析獲取TestDbContext對象。

          代碼如下:

          public classTestBgService:BackgroundService{       private readonly IServiceScopeFactory scopeFactory;       public TestBgService(IServiceScopeFactory scopeFactory)       {              this.scopeFactory= scopeFactory;       }        protected override Task ExecuteAsync(CancellationToken stoppingToken)       {              using(var scope = scopeFactory.CreateScope())              {                     var sp = scope.ServiceProvider;                     var dbCtx = sp.GetRequiredService<TestDbContext>();                     foreach(var b in dbCtx.Books)                     {                            Console.WriteLine(b.Title);                     }              }                             return Task.CompletedTask;       }}


           

          用法3、異步方法可以不await

          我在做youzack背單詞的時候,有一個查詢單詞的功能。為了提升客戶端的響應(yīng)速度,我把每個單詞的明細(xì)信息都按照“每個單詞一個json文件”的形式,把單詞的詳細(xì)信息保存到文件服務(wù)器,相當(dāng)于做了一個“靜態(tài)化”。因此客戶端在查詢單詞的時候,先到文件服務(wù)器中查找一下是否有對應(yīng)的靜態(tài)文件,如果有的話,就直接加載靜態(tài)文件。如果在文件服務(wù)器不存在的話,再調(diào)用API接口的方法去查詢,API接口從數(shù)據(jù)庫中查詢到單詞后,不僅會把單詞的詳細(xì)信息返回給客戶端,而且還會把單詞的詳細(xì)信息再上傳到文件服務(wù)器。這樣以后客戶端再查詢這個單詞,就可以直接從文件服務(wù)器查詢了。


          因此API接口中“把從數(shù)據(jù)庫中查詢到的單詞的詳細(xì)信息上傳到文件服務(wù)器”這個操作對于接口的請求者來講沒什么意義,而且會降低接口的響應(yīng)速度,因此我就把“上傳到文件服務(wù)器”這個操作寫到了異步方法中,并且沒有通過await來等待。

          偽代碼如下:

          public async Task<WordDetail>FindWord(string word){       var detail = await db.FindWordInDBAsync(word);//從數(shù)據(jù)庫里查詢       _=storage.UploadAsync($”{word}.json”,detail.ToJsonString());//上傳到文件服務(wù)器,但是不等待       returnd etail;}


           

          在上面的UploadAsync調(diào)用中沒有await調(diào)用等待,因此只要從數(shù)據(jù)庫中查詢出來,就把detail返回給請求者了,留下UploadAsync在異步線程中慢慢執(zhí)行。

           

          前面加的“_=”是消除對于不await異步方法造成編譯器警告。

           

          用法4、異步代碼中Sleep的坑

           

              在編寫代碼的時候,有時候我們需要“暫停一段時間,再繼續(xù)執(zhí)行代碼”。比如調(diào)用一個Http接口,如果調(diào)用失敗,則需要等待2秒鐘再重試。

              在異步方法中,如果需要“暫停一段時間”,那么請使用Task.Delay(),而不是Thread.Sleep(),因為Thread.Sleep()會阻塞主線程,就達(dá)不到“使用異步提升系統(tǒng)并發(fā)能力”的目的了。

          如下代碼是錯誤的:

          public async Task<IActionResult> TestSleep(){       await System.IO.File.ReadAllTextAsync("d:/temp/words.txt");       Console.WriteLine("firstdone");       Thread.Sleep(2000);       awaitSystem.IO.File.ReadAllTextAsync("d:/temp/words.txt");       Console.WriteLine("seconddone");       returnContent("xxxxxx");}


          上面的代碼是能夠正確的編譯執(zhí)行的,但是會大大降低系統(tǒng)的并發(fā)處理能力。因此要用Task.Delay()代替Thread.Sleep()。如下是正確的:

          public async Task<IActionResult> TestSleep(){       awaitSystem.IO.File.ReadAllTextAsync("d:/temp/words.txt");       Console.WriteLine("firstdone");       awaitTask.Delay(2000);//!!!       awaitSystem.IO.File.ReadAllTextAsync("d:/temp/words.txt");       Console.WriteLine("seconddone");       returnContent("xxxxxx");}


           

          用法5、yield如何用到異步方法中

              yield由于可以實現(xiàn)“產(chǎn)生一個數(shù)據(jù)就讓IEnumerable的使用者處理一個數(shù)據(jù)”,從而實現(xiàn)數(shù)據(jù)處理的“流水線化”,提升數(shù)據(jù)處理的速度。

              但是,由于yield和async都是編譯器提供的語法糖,編譯器都會把它們修飾的方法編譯為一個使用了狀態(tài)機(jī)的類。因此兩個語法糖碰到一起,編譯器就迷惑了,因此不能直接在async修飾的異步方法中使用yield返回數(shù)據(jù)。

          因此下面的代碼是錯誤的:

          static async IEnumerable<int>ReadCC(){       foreach(string line in await File.ReadAllLinesAsync("d:/temp/words.txt"))       {              yieldreturn line.Length;       }}


          只要把IEnumerable改成IAsyncEnumerable就可以了,如下是正確的:

          static async IAsyncEnumerable<int>ReadCC(){       foreach(stringline in await File.ReadAllLinesAsync("d:/temp/words.txt"))       {              yieldreturn line.Length;       }}


          但是調(diào)用同時使用了async和yield的代碼,不能使用普通的foreach+await,如下是錯誤的:

          foreach (int i in await ReadCC()){       Console.WriteLine(i);}


           

          需要把a(bǔ)wait關(guān)鍵詞移動到在foreach之前,如下是正確的:

          await foreach(int i in ReadCC()){       Console.WriteLine(i);}


          編譯器是微軟寫的,不知道為什么不支持foreach (int i in awaitReadCC())這樣的寫法,可能是由于為了兼容之前的C#語法規(guī)范不得已而為之吧。


          往期精彩回顧




          【推薦】.NET Core開發(fā)實戰(zhàn)視頻課程 ★★★

          .NET Core實戰(zhàn)項目之CMS 第一章 入門篇-開篇及總體規(guī)劃

          【.NET Core微服務(wù)實戰(zhàn)-統(tǒng)一身份認(rèn)證】開篇及目錄索引

          Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

          .NET Core中的一個接口多種實現(xiàn)的依賴注入與動態(tài)選擇看這篇就夠了

          10個小技巧助您寫出高性能的ASP.NET Core代碼

          用abp vNext快速開發(fā)Quartz.NET定時任務(wù)管理界面

          在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實現(xiàn)作業(yè)調(diào)度

          現(xiàn)身說法:實際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

          關(guān)于C#異步編程你應(yīng)該了解的幾點建議

          C#異步編程看這篇就夠了


          瀏覽 49
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  www.人人撸 | 日韩91成人精品久久久电影 | 老熟仑妇乱一区二区AV | 在线做爱 | 欧美日韩视频在线播放 |