<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# 高級(jí):TAP 異步編程

          共 7904字,需瀏覽 16分鐘

           ·

          2021-12-10 10:38

          .NET大牛之路 ? 王亮@精致碼農(nóng) ? 2021.10.12

          我們的應(yīng)用程序廣泛使用文件和網(wǎng)絡(luò) I/O 操作,I/O 相關(guān) API 傳統(tǒng)上默認(rèn)是阻塞的,導(dǎo)致用戶體驗(yàn)和硬件利用率不佳,此類問(wèn)題的編碼難度也較大。

          解決此類問(wèn)題需要使用異步編程,異步強(qiáng)調(diào)的是非阻塞,是一種編程模式,主要解決了因文件、網(wǎng)絡(luò)等 I/O 操作阻塞主線程工作的問(wèn)題,比如阻塞期間 UI 無(wú)法響應(yīng)問(wèn)題。

          而異步編程又可以借助多線程技術(shù)來(lái)解決。前面我們講了基于 System.Threading 命名空間的多線程編程,該命名空間提供的類型是直接和線程相關(guān)的 API,雖然可以用來(lái)實(shí)現(xiàn)異步操作,但有些繁瑣。隨著 .NET 的發(fā)展,.NET 對(duì)多線程編程相繼做了進(jìn)一步的抽象封裝,引入了 System.Threading.Tasks 命名空間,使多線程異步編程更簡(jiǎn)單易懂。

          異步編程主要有如下用途:

          • 在等待 I/O 請(qǐng)求返回的過(guò)程中,通過(guò)讓出線程使其能處理更多的服務(wù)器請(qǐng)求。

          • 在等待 I/O 請(qǐng)求時(shí)讓出線程使其繼續(xù)進(jìn)行 UI 交互,并將需要長(zhǎng)時(shí)間運(yùn)行的工作過(guò)渡到其他 CPU 線程,使用戶界面的響應(yīng)性更強(qiáng)。

          使用 .NET 基于 Task 的異步模型可以直接編寫 I/O 受限和 CPU 受限的異步代碼。該模型圍繞著 TaskTask 類型以及 C# 的 asyncawait 關(guān)鍵字展開(kāi)。本文將講解如何使用 .NET 異步編程及一些常見(jiàn)的異步編程操作。

          1Task 和 Task

          Task 是 Promise 模型的實(shí)現(xiàn)。簡(jiǎn)單說(shuō),它給出“承諾(Promise)”:會(huì)在稍后完成工作。而 .NET 的 Task 是為了簡(jiǎn)化使用“Promise”而設(shè)計(jì)的 API。

          Task 表示不返回值的操作,Task 表示返回 T 類型的值的操作。

          重要的是要把 Task 理解為發(fā)起異步工作的抽象,而不是對(duì)線程的抽象。默認(rèn)情況下,Task 在當(dāng)前線程上執(zhí)行,并酌情將工作委托給操作系統(tǒng)??梢赃x擇通過(guò) Task.Run API 明確要求任務(wù)在單獨(dú)的線程上運(yùn)行。

          Task 提供了一個(gè) API 協(xié)議,用于監(jiān)視、等待和訪問(wèn)任務(wù)的結(jié)果值。比如,通過(guò) await 關(guān)鍵字等待任務(wù)執(zhí)行完成,為使用 Task 提供了更高層次的抽象。

          使用 await 允許你在任務(wù)運(yùn)行期間執(zhí)行其它有用的工作,將線程的控制權(quán)交給其它調(diào)用者,直到自己的任務(wù)完成。你不再需要依賴回調(diào)或事件來(lái)在任務(wù)完成后繼續(xù)執(zhí)行后續(xù)工作。

          2Task 的狀態(tài)

          雖然實(shí)際 TAP 編程中很少使用到 Task 的狀態(tài),但它是很多異步操作機(jī)理的基礎(chǔ)。Task 類為異步操作提供了一個(gè)生命周期,這個(gè)周期由 TaskStatus 枚舉表示,它有如下值:

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

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

          3I/O 受限異步操作

          下面示例代碼演示了一個(gè)典型的異步 I/O 調(diào)用操作:

          public Task<string> GetHtmlAsync()
          {
          // 此處是同步執(zhí)行
          var client = new HttpClient();
          return client.GetStringAsync("https://www.dotnetfoundation.org");
          }

          這個(gè)例子調(diào)用了一個(gè)異步方法,并返回了一個(gè)活動(dòng)的 Task,它很可能還沒(méi)有完成。

          下面第二個(gè)代碼示例增加了asyncawait關(guān)鍵字對(duì)任務(wù)進(jìn)行操作:

          public?async Task<string> GetFirstCharactersCountAsync(string url, int count)
          {
          // 此處是同步執(zhí)行
          var client = new HttpClient();

          // 此處 await 掛起代碼的執(zhí)行,把控制權(quán)交出去(線程可以去做別的事情)
          var page = await client.GetStringAsync("https://www.dotnetfoundation.org");

          // 任務(wù)完成后恢復(fù)了控制權(quán),繼續(xù)執(zhí)行后續(xù)代碼
          // 此處回到了同步執(zhí)行

          if (count > page.Length)
          {
          return page;
          }
          else
          {
          return page.Substring(0, count);
          }
          }

          使用 await 關(guān)鍵字告訴當(dāng)前上下文趕緊生成快照并交出控制權(quán),異步任務(wù)執(zhí)行完成后會(huì)帶著返回值去線程池排隊(duì)等待可用線程,等到可用線程后,恢復(fù)上下文,線程繼續(xù)執(zhí)行后續(xù)代碼。

          GetStringAsync() 方法的內(nèi)部通過(guò)底層 .NET 庫(kù)調(diào)用資源(也許會(huì)調(diào)用其他異步方法),一直到 P/Invoke 互操作調(diào)用本地(Native)網(wǎng)絡(luò)庫(kù)。本地庫(kù)隨后可能會(huì)調(diào)用到一個(gè)系統(tǒng) API(如 Linux 上 Socket 的write()API)。Task 對(duì)象將通過(guò)層層傳遞,最終返回給初始調(diào)用者。

          在整個(gè)過(guò)程中,關(guān)鍵的一點(diǎn)是,沒(méi)有一個(gè)線程是專門用來(lái)處理任務(wù)的。雖然工作是在某種上下文中執(zhí)行的(操作系統(tǒng)確實(shí)要把數(shù)據(jù)傳遞給設(shè)備驅(qū)動(dòng)程序并中斷響應(yīng)),但沒(méi)有線程專門用來(lái)等待請(qǐng)求的數(shù)據(jù)回返回。這使得系統(tǒng)可以處理更大的工作量,而不是干等著某個(gè) I/O 調(diào)用完成。

          雖然上面的工作看似很多,但與實(shí)際 I/O 工作所需的時(shí)間相比,簡(jiǎn)直微不足道。用一條不太精確的時(shí)間線來(lái)表示,大概是這樣的:

          0-1--------------------2-3

          01所花費(fèi)的時(shí)間是await交出控制權(quán)之前所花的時(shí)間。從12花費(fèi)的時(shí)間是GetStringAsync方法花費(fèi)在 I/O 上的時(shí)間,沒(méi)有 CPU 成本。最后,從23花費(fèi)的時(shí)間是上下文重新獲取控制權(quán)后繼續(xù)執(zhí)行的時(shí)間。

          4CPU 受限異步操作

          CPU 受限的異步代碼與 I/O 受限的異步代碼有些不同。因?yàn)楣ぷ魇窃?CPU 上完成的,所以沒(méi)有辦法繞開(kāi)專門的線程來(lái)進(jìn)行計(jì)算。使用 async 和 await 只是為你提供了一種干凈的方式來(lái)與后臺(tái)線程進(jìn)行交互。請(qǐng)注意,這并不能為共享數(shù)據(jù)提供加鎖保護(hù),如果你正在使用共享數(shù)據(jù),仍然需要使用適當(dāng)?shù)耐讲呗浴?/p>

          下面是一個(gè) CPU 受限的異步調(diào)用:

          public?async Task<int> CalculateResult(InputData data)
          {
          // 在線程池排隊(duì)獲取線程來(lái)處理任務(wù)
          var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));

          // 此時(shí)此處,你可以并行地處理其它工作

          var result = await expensiveResultTask;

          return result;
          }

          CalculateResult方法在它被調(diào)用的線程(一般可以定義為主線程)上執(zhí)行。當(dāng)它調(diào)用Task.Run時(shí),會(huì)在線程池上排隊(duì)執(zhí)行 CPU 受限操作 DoExpensiveCalculation,并接收一個(gè)Task句柄。DoExpensiveCalculation會(huì)在下一個(gè)可用的線程上并行運(yùn)行,很可能是在另一個(gè) CPU 核上。和 I/O 受限異步調(diào)用一樣,一旦遇到await,CalculateResult的控制權(quán)就會(huì)被交給它的調(diào)用者,這樣在DoExpensiveCalculation返回結(jié)果的時(shí)候,結(jié)果就會(huì)被安排在主線程上排隊(duì)運(yùn)行。

          對(duì)于開(kāi)發(fā)者,CPU 受限和 I/O 受限的在調(diào)用方式上沒(méi)什么區(qū)別。區(qū)別在于所調(diào)用資源性質(zhì)的不同,不必關(guān)心底層對(duì)不同資源的調(diào)用的具體邏輯。編寫代碼需要考慮的是,對(duì)于 CPU 受限的異步任務(wù),根據(jù)實(shí)際情況考慮是否需要使其和其它任務(wù)并行執(zhí)行,以加快程序的整體運(yùn)行時(shí)間。

          5異步編程模式

          最后簡(jiǎn)單回顧一下 .NET 歷史上提供的三種執(zhí)行異步操作的模式。

          • 基于任務(wù)的異步模式(Task-based Asynchronous Pattern,TAP),它使用單一的方法來(lái)表示異步操作的啟動(dòng)和完成。TAP 是在 .NET Framework 4 中引入的。它是 .NET 中異步編程的推薦方法。C# 中的 async 和 await 關(guān)鍵字為 TAP 添加了語(yǔ)言支持。

          • 基于事件的異步模式(Event-based Asynchronous Pattern,EAP),這是基于事件的傳統(tǒng)模式,用于提供異步行為。它需要一個(gè)具有 Async 后綴的方法和一個(gè)或多個(gè)事件。EAP 是在 .NET Framework 2.0 中引入的。它不再被推薦用于新的開(kāi)發(fā)。

          • 異步編程模式(Asynchronous Programming Model,APM)模式,也稱為 IAsyncResult 模式,這是使用 IAsyncResult 接口提供異步行為的傳統(tǒng)模式。在這種模式中,需要BeginEnd方法同步操作(例如,BeginWriteEndWrite來(lái)實(shí)現(xiàn)異步寫操作)。這種模式也不再推薦用于新的開(kāi)發(fā)。

          下面簡(jiǎn)單舉例對(duì)三種模式進(jìn)行比較。

          假設(shè)有一個(gè) Read 方法,該方法從指定的偏移量開(kāi)始將指定數(shù)量的數(shù)據(jù)讀入提供的緩沖區(qū):

          public?class?MyClass
          {
          public?int?Read(byte [] buffer, int offset, int count);
          }

          若用 TAP 異步模式來(lái)改寫,該方法將是簡(jiǎn)單的一個(gè) ReadAsync 方法:

          public?class?MyClass
          {
          public Task<int> ReadAsync(byte [] buffer, int offset, int count);
          }

          若使用 EAP 異步模式,需要額外多定義一些類型和成員:

          public?class?MyClass
          {
          public?void?ReadAsync(byte [] buffer, int offset, int count);
          public?event ReadCompletedEventHandler ReadCompleted;
          }

          public?delegate?void?ReadCompletedEventHandler(
          object sender, ReadCompletedEventArgs e);

          public?class?ReadCompletedEventArgs : AsyncCompletedEventArgs
          {
          public MyReturnType Result { get; }
          }

          若使用 AMP 異步模式,則需要定義兩個(gè)方法,一個(gè)用于開(kāi)始執(zhí)行異步操作,一個(gè)用于接收異步操作結(jié)果:

          public?class?MyClass
          {
          public IAsyncResult BeginRead(
          byte [] buffer, int offset, int count,
          AsyncCallback callback, object state);
          public?int?EndRead(IAsyncResult asyncResult);
          }

          后兩種異步模式已經(jīng)過(guò)時(shí)不推薦使用了,這里也不再繼續(xù)探討。年長(zhǎng)的 .NET 程序員可能比較熟悉后兩種異步模式,畢竟那時(shí)候沒(méi)有 async/await,應(yīng)該沒(méi)少折騰。

          下面來(lái)介紹幾個(gè)常見(jiàn)的基于 TAP 的異步操作。

          6手動(dòng)控制任務(wù)啟動(dòng)

          為了支持手動(dòng)控制任務(wù)啟動(dòng),并支持構(gòu)造與調(diào)用的分離,Task 類提供了一個(gè) Start 方法。由 Task 構(gòu)造函數(shù)創(chuàng)建的任務(wù)被稱為冷任務(wù),因?yàn)樗鼈兊纳芷谔幱?Created 狀態(tài),只有該實(shí)例的 Start 方法被調(diào)用才會(huì)啟動(dòng)。

          任務(wù)狀態(tài)平時(shí)用的情況不多,一般我們?cè)诜庋b一個(gè)任務(wù)相關(guān)的方法時(shí),可能會(huì)用到。比如下面這個(gè)例子,需要判斷某任務(wù)滿足一定條件才啟動(dòng):

          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
          {
          // 這里模擬計(jì)數(shù)業(yè)務(wù)代碼,直到 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 方法激活過(guò)的。

          7確保任務(wù)已激活

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

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

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

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

          8任務(wù)取消

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

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

          異步操作會(huì)監(jiān)控這個(gè)令牌是否有取消請(qǐng)求。如果收到取消請(qǐng)求,它可以選擇取消操作,如下面的示例通過(guò) while 來(lái)監(jiān)控令牌的取消請(qǐng)求:

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

          var task = DoWork(token);

          // 實(shí)際情況可能是在稍后的其它線程請(qǐng)求取消
          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);
          }

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

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

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

          9進(jìn)度報(bào)告

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

          在 TAP 中,進(jìn)度是通過(guò) IProgress 接口來(lái)處理的,該接口作為一個(gè)參數(shù)傳遞給異步方法。下面是一個(gè)典型的的使用示例:

          static?void?Main(string[] args)
          {
          var progress = new Progress<int>(n =>
          {
          Console.WriteLine($"當(dāng)前進(jìn)度:{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é)果:

          當(dāng)前進(jìn)度:10%
          當(dāng)前進(jìn)度:20%
          當(dāng)前進(jìn)度:30%
          當(dāng)前進(jìn)度:40%
          當(dāng)前進(jìn)度:50%
          當(dāng)前進(jìn)度:60%
          當(dāng)前進(jìn)度:70%
          當(dāng)前進(jìn)度:80%
          當(dāng)前進(jìn)度:90%
          當(dāng)前進(jìn)度:100%

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

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

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

          public Task> FindFilesAsync(
          string pattern,
          IProgressdouble, ReadOnlyCollection>>> progress)

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

          public Task> FindFilesAsync(
          string pattern,
          IProgress progress)

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

          10Task.Yield 讓步

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

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

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

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

          Task.Yield() 方法就是在異步方法中引入一個(gè)讓步點(diǎn)。當(dāng)代碼執(zhí)行到讓步點(diǎn)時(shí),就會(huì)讓出控制權(quán),去線程池外面兜一圈什么事也沒(méi)干再回來(lái)重新排隊(duì)。

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

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

          ConfigureAwait

          我們先來(lái)看一段 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 程序中會(huì)造成死鎖。原因是 UI 主線程執(zhí)行到這句代碼時(shí),就開(kāi)始等待異步任務(wù)的結(jié)果,處于阻塞狀態(tài)。而異步任務(wù)執(zhí)行完后回來(lái)準(zhǔn)備找 UI 線程繼續(xù)執(zhí)行后面的代碼時(shí),卻發(fā)現(xiàn) UI 線程一直處于“忙碌”的狀態(tài),沒(méi)空搭理回來(lái)的異步任務(wù)。這就造成了你等我,我又在等你的尷尬局面。

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

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

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

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

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

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

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

          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);
          }

          如上,可以一直鏈?zhǔn)降膶懴氯ィ蝿?wù)會(huì)按照順序執(zhí)行,一個(gè)執(zhí)行完再繼續(xù)執(zhí)行下一個(gè)。若其中一個(gè)任務(wù)返回的狀態(tài)是 Canceled 時(shí),后續(xù)的任務(wù)也將被取消。這個(gè)方法有好些個(gè)重載,在實(shí)際用到的時(shí)候再查看文檔即可。

          12小結(jié)

          System.Threading.Tasks 命名空間中關(guān)鍵的一個(gè)類是 Task 類,基于 Task 的異步 API 和語(yǔ)言級(jí)異步編程模式顛覆了傳統(tǒng)模式,使得異步編程非常簡(jiǎn)單。它使我們可以只關(guān)注業(yè)務(wù)層面要處理的任務(wù),而不必關(guān)心和使用線程或線程池。重要的是要把 Task 理解為發(fā)起異步工作的抽象,而不是對(duì)線程的抽象。本文還介紹了 .NET 異步編程模式,而我們現(xiàn)在主流用的都是 TAP 模式,最后本文羅列一些常見(jiàn)的異步操作。

          瀏覽 41
          點(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>
                  亚洲成人资源网 | 婷婷亚洲激情 | 国产内射一级视频 | 日韩精品成人一区二区三区蜜桃 | 男人天堂国产精品 |