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

          C#.NET 幾個常見的TAP異步操作

          共 7692字,需瀏覽 16分鐘

           ·

          2021-04-13 22:00

          在本系列上一篇文章 [15:異步編程基礎(chǔ)] 中,我們講到,現(xiàn)代應(yīng)用程序廣泛使用的是基于任務(wù)的異步編程模式(TAP),歷史的 EAP 和 AMP 模式已經(jīng)過時不推薦使用。今天繼續(xù)總結(jié)一下 TAP 的異步操作,比如取消任務(wù)、報告進度、Task.Yield()ConfigureAwait() 和并行操作等。

          雖然實際 TAP 編程中很少使用到任務(wù)的狀態(tài),但它是很多 TAP 操作機理的基礎(chǔ),所以下面先從任務(wù)狀態(tài)講起。

          1任務(wù)狀態(tài)

          Task 類為異步操作提供了一個生命周期,這個周期由 TaskStatus 枚舉表示,它有如下值:

          public enum TaskStatus
          {
          Created = 0,
          WaitingForActivation = 1,
          WaitingToRun = 2,
          Running = 3,
          WaitingForChildrenToComplete = 4,
          RanToCompletion = 5,
          Canceled = 6,
          Faulted = 7
          }

          其中 CanceledFaultedRanToCompletion 狀態(tài)一起被認為是任務(wù)的最終狀態(tài)。因此,如果任務(wù)處于最終狀態(tài),則其 IsCompleted 屬性為 true 值。

          手動控制任務(wù)啟動

          為了支持手動控制任務(wù)啟動,并支持構(gòu)造與調(diào)用的分離,Task 類提供了一個 Start 方法。由 Task 構(gòu)造函數(shù)創(chuàng)建的任務(wù)被稱為冷任務(wù),因為它們的生命周期處于 Created 狀態(tài),只有該實例的 Start 方法被調(diào)用才會啟動。

          任務(wù)狀態(tài)平時用的情況不多,一般我們在封裝一個任務(wù)相關(guān)的方法時,可能會用到。比如下面這個例子,需要判斷某任務(wù)滿足一定條件才啟動:

          static void Main(string[] args)
          {
          MyTask t = new(() =>
          {
          // do something.
          });

          StartMyTask(t);

          Console.ReadKey();
          }

          public static void StartMyTask(MyTask t)
          {
          if (t.Status == TaskStatus.Created && t.Counter>10)
          {
          t.Start();
          }
          else
          {
          // 這里模擬計數(shù),直到 Counter>10 再執(zhí)行 Start
          while (t.Counter <= 10)
          {
          // Do something
          t.Counter++;
          }
          t.Start();
          }
          }

          public class MyTask : Task
          {
          public MyTask(Action action) : base(action)
          {
          }

          public int Counter { get; set; }
          }

          同樣,TaskStatus.Created 狀態(tài)以外的狀態(tài),我們叫它熱任務(wù),熱任務(wù)一定是被調(diào)用了 Start 方法激活過的。

          確保任務(wù)已激活

          注意,所有從 TAP 方法返回的任務(wù)都必須被激活,比如下面這樣的代碼:

          MyTask task = new(() =>
          {
          Console.WriteLine("Do something.");
          });

          // 在其它地方調(diào)用
          await task;

          await 之前,任務(wù)沒有執(zhí)行 Task.Start 激活,await 時程序就會一直等待下去。所以如果一個 TAP 方法內(nèi)部使用 Task 構(gòu)造函數(shù)來實例化要返回的 Task,那么 TAP 方法必須在返回 Task 對象之前對其調(diào)用 Start

          2任務(wù)取消

          在 TAP 中,取消對于異步方法實現(xiàn)者和消費者來說都是可選的。如果一個操作允許取消,它就會暴露一個異步方法的重載,該方法接受一個取消令牌(CancellationToken 實例)。按照慣例,參數(shù)被命名為 cancellationToken。例如:

          public Task ReadAsync(
          byte [] buffer, int offset, int count,
          CancellationToken cancellationToken)

          異步操作會監(jiān)控這個令牌是否有取消請求。如果收到取消請求,它可以選擇取消操作,如下面的示例通過 while 來監(jiān)控令牌的取消請求:

          static void Main(string[] args)
          {
          CancellationTokenSource source = new();
          CancellationToken token = source.Token;

          var task = DoWork(token);

          // 實際情況可能是在稍后的其它線程請求取消
          Thread.Sleep(100);
          source.Cancel();

          Console.WriteLine($"取消后任務(wù)返回的狀態(tài):{task.Status}");

          Console.ReadKey();
          }

          public static Task DoWork(CancellationToken cancellationToken)
          {
          while (!cancellationToken.IsCancellationRequested)
          {
          // Do something.
          Thread.Sleep(1000);

          return Task.CompletedTask;
          }
          return Task.FromCanceled(cancellationToken);
          }

          如果取消請求導(dǎo)致工作提前結(jié)束,甚至還沒有開始就收到請求取消,則 TAP 方法返回一個以 Canceled 狀態(tài)結(jié)束的任務(wù),它的 IsCompleted 屬性為 true,且不會拋出異常。當任務(wù)在 Canceled 狀態(tài)下完成時,任何在該任務(wù)注冊的延續(xù)任務(wù)仍都會被調(diào)用和執(zhí)行,除非指定了諸如 NotOnCanceled 這樣的選項來選擇不延續(xù)。

          但是,如果在異步任務(wù)在工作時收到取消請求,異步操作也可以選擇不立刻結(jié)束,而是等當前正在執(zhí)行的工作完成后再結(jié)束,并返回 RanToCompletion 狀態(tài)的任務(wù);也可以終止當前工作并強制結(jié)束,根據(jù)實際業(yè)務(wù)情況和是否生產(chǎn)異常結(jié)果返回 CanceledFaulted 狀態(tài)。

          對于不能被取消的業(yè)務(wù)方法,不要提供接受取消令牌的重載,這有助于向調(diào)用者表明目標方法是否可以取消。

          3進度報告

          幾乎所有異步操作都可以提供進度通知,這些通知通常用于用異步操作的進度信息更新用戶界面。

          在 TAP 中,進度是通過 IProgress<T> 接口來處理的,該接口作為一個參數(shù)傳遞給異步方法。下面是一個典型的的使用示例:

          static void Main(string[] args)
          {
          var progress = new Progress<int>(n =>
          {
          Console.WriteLine($"當前進度:{n}%");
          });

          var task = DoWork(progress);

          Console.ReadKey();
          }

          public static async Task DoWork(IProgress<int> progress)
          {
          for (int i = 1; i <= 100; i++)
          {
          await Task.Delay(100);
          if (i % 10 == 0)
          {
          progress?.Report(i);
          };
          }
          }

          輸出如下結(jié)果:

          當前進度:10%
          當前進度:20%
          當前進度:30%
          當前進度:40%
          當前進度:50%
          當前進度:60%
          當前進度:70%
          當前進度:80%
          當前進度:90%
          當前進度:100%

          IProgress<T> 接口支持不同的進度實現(xiàn),這是由消費代碼決定的。例如,消費代碼可能只關(guān)心最新的進度更新,或者希望緩沖所有更新,或者希望為每個更新調(diào)用一個操作,等等。所有這些選項都可以通過使用該接口來實現(xiàn),并根據(jù)特定消費者的需求進行定制。例如,如果本文前面的 ReadAsync 方法能夠以當前讀取的字節(jié)數(shù)的形式報告進度,那么進度回調(diào)可以是一個 IProgress<long> 接口。

          public Task ReadAsync(
          byte[] buffer, int offset, int count,
          IProgress<long> progress)

          再如 FindFilesAsync 方法返回符合特定搜索模式的所有文件列表,進度回調(diào)可以提供工作完成的百分比和當前部分結(jié)果集,它可以用一個元組來提供這個信息。

          public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
          string pattern,
          IProgress<Tuple<double, ReadOnlyCollection<List<FileInfo>>>> progress)

          或使用 API 特有的數(shù)據(jù)類型:

          public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
          string pattern,
          IProgress<FindFilesProgressInfo> progress)

          如果 TAP 的實現(xiàn)提供了接受 IProgress<T> 參數(shù)的重載,它們必須允許參數(shù)為空,在這種情況下,不會報告進度。IProgress<T> 實例可以作為獨立的對象,允許調(diào)用者決定如何以及在哪里處理這些進度信息。

          4Task.Yield 讓步

          我們先來看一段 Task.Yield() 的代碼:

          Task.Run(async () =>
          {
          for(int i=0; i<10; i++)
          {
          await Task.Yield();
          ...
          }
          });

          這里的 Task.Yield() 其實什么也沒干,它返回的是一個空任務(wù)。那 await 一個什么也沒做的空任務(wù)有什么用呢?

          我們知道,對計算機來說,任務(wù)調(diào)度是根據(jù)一定的優(yōu)先策略來安排線程去執(zhí)行的。如果任務(wù)太多,線程不夠用,任務(wù)就會進入排隊狀態(tài)。而 Yield 的作用就是讓出等待的位置,讓后面排除的任務(wù)先行。它字面上的意思就是讓步,當任務(wù)做出讓步時,其它任務(wù)就可以盡快被分配線程去執(zhí)行。舉個現(xiàn)實生活中的例子,就像你在排隊辦理業(yè)務(wù)時,好不容易到你了,但你的事情并不急,自愿讓出位置,讓其他人先辦理,自己假裝臨時有事到外面溜一圈什么事也沒干又回來重新排隊。默默地做了一次大善人。

          Task.Yield() 方法就是在異步方法中引入一個讓步點。當代碼執(zhí)行到讓步點時,就會讓出控制權(quán),去線程池外面兜一圈什么事也沒干再回來重新排隊。

          5定制異步任務(wù)后續(xù)操作

          我們可以對異步任務(wù)執(zhí)行完成的后續(xù)操作進行定制。常見的兩個方法是 ConfigureAwaitContinueWith

          ConfigureAwait

          我們先來看一段 Windows Form 中的代碼:

          private void button1_Click(object sender, EventArgs e)
          {
          var content = CurlAsync().Result;
          ...
          }

          private async Task<string> CurlAsync()
          {
          using (var client = new HttpClient())
          {
          return await client.GetStringAsync("http://geekgist.com");
          }
          }

          想必大家都知道 CurlAsync().Result 這句代碼在 Windows Form 程序中會造成死鎖。原因是 UI 主線程執(zhí)行到這句代碼時,就開始等待異步任務(wù)的結(jié)果,處于阻塞狀態(tài)。而異步任務(wù)執(zhí)行完后回來準備找 UI 線程繼續(xù)執(zhí)行后面的代碼時,卻發(fā)現(xiàn) UI 線程一直處于“忙碌”的狀態(tài),沒空搭理回來的異步任務(wù)。這就造成了你等我,我又在等你的尷尬局面。

          當然,這種死鎖的情況只會在 Winform 和早期的 ASP.NET WebForm 中才會發(fā)生,在 Console 和 Web API 應(yīng)用中不會生產(chǎn)死鎖。

          解決辦法很簡單,作為異步方法調(diào)用者,我們只需改用 await 即可:

          private async void button1_Click(object sender, EventArgs e)
          {
          var content = await CurlAsync();
          ...
          }

          在異步方法內(nèi)部,我們也可以調(diào)用任務(wù)的 ConfigureAwait(false) 方法來解決這個問題。如:

          private async Task<string> CurlAsync()
          {
          using (var client = new HttpClient())
          {
          return await client
          .GetStringAsync("http://geekgist.com")
          .ConfigureAwait(false);
          }
          }

          雖然兩種方法都可行,但如果作為異步方法提供者,比如封裝一個通用庫時,考慮到難免會有新手開發(fā)者會使用 CurlAsync().Result,為了提高通用庫的容錯性,我們就可能需要使用 ConfigureAwait 來做兼容。

          ConfigureAwait(false) 的作用是告訴主線程,我要去遠行了,你去做其它事情吧,不用等我。只要先確保一方不在一直等另一方,就能避免互相等待而造成死鎖的情況。

          ContinueWith

          ContinueWith 方法很容易理解,就是字面上的意思。作用是在異步任務(wù)執(zhí)行完成后,安排后續(xù)要執(zhí)行的工作。示例代碼:

          private void Button1_Click(object sender, EventArgs e)
          {
          var backgroundScheduler = TaskScheduler.Default;
          var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
          Task.Factory
          .StartNew(_ => DoBackgroundComputation(), backgroundScheduler)
          .ContinueWith(_ => UpdateUI(), uiScheduler)
          .ContinueWith(_ => DoAnotherBackgroundComputation(), backgroundScheduler)
          .ContinueWith(_ => UpdateUIAgain(), uiScheduler);
          }

          如上,可以一直鏈式的寫下去,任務(wù)會按照順序執(zhí)行,一個執(zhí)行完再繼續(xù)執(zhí)行下一個。若其中一個任務(wù)返回的狀態(tài)是 Canceled 時,后續(xù)的任務(wù)也將被取消。這個方法有好些個重載,在實際用到的時候再查看文檔即可。

          6總結(jié)

          本文內(nèi)容都是相對比較基礎(chǔ)的 TAP 異步操作知識點。C# 的 TAP 很強大,提供的 API 也很多,遠不止本文講的這些,都是圍繞 Task 轉(zhuǎn)的。關(guān)鍵是要理解好基礎(chǔ)操作,才能靈活使用更高級的功能。希望本文對你有所幫助。

          參考:

          https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-taphttps://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/implementing-the-task-based-asynchronous-patternhttps://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern







          回復(fù) 【關(guān)閉】關(guān)
          回復(fù) 【實戰(zhàn)】獲取20套實戰(zhàn)源碼
          回復(fù) 【被刪】
          回復(fù) 【訪客】
          回復(fù) 【小程序】學獲取15套【入門+實戰(zhàn)+賺錢】小程序源碼
          回復(fù) 【python】學微獲取全套0基礎(chǔ)Python知識手冊
          回復(fù) 【2019】獲取2019 .NET 開發(fā)者峰會資料PPT
          回復(fù) 【加群】加入dotnet微信交流群

          人人影視字幕組涼了,這款美劇APP不能錯過!


          谷歌靈魂插件,98%的程序員都好評!


          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产精品自产拍在线观看 | 日本黄色一区二区三区 | 欧美一级黄色录像在线视频官网 | 五月色网站 | 久久性爱影院 |