【異步編程】Part1:await&async語法糖讓異步編程如魚得水


前導

  Asynchronous programming Model(APM)異步編程模型以BeginMethod(...) 和 EndMethod(...)結對出現。
IAsyncResult  BeginGetResponse(AsyncCallback callback, object state)
WebResponse  EndGetResponse(IAsyncResult asyncResult)

   Event-based Asynchronous Pattern(EAP)模型以MethodAsync(...) 和CancelAsync(...) 結對出現,由Completed事件設置回調函數。

WebClient類中通過DownloadStringAsync方法開啟一個異步任務,並由DownloadStringCompleted事件供設置回調函數,能通過CancelAsync方法取消異步任務。

  .Net4.5開始Task Parallel Library(TPL) 為異步和並行編程提供新的模型,使異步和並發操作有統一的編程入口,

該模型常定義以Async后綴結尾的函數名、返回帶有awaitable屬性的Task/Task<T>對象, 如果你的program target設置為4.5+,可用Task-based Asynchronous Pattern (TAP)取代以上2種模型。

 

TAP

  TPL的核心是Task類,Task,Task<TResult>可以將其理解為一個包裝委托(通常就是Action或Func委托)並執行的容器,有了Task幾乎不用去和Thread 打交道,使用者只需要關注具體業務對應的Job,Task背后有一個 TaskScheduler 的類來負責調度Task的執行,這樣Task對象將在默認的TaskScheduler調度下執行,TaskScheduler使用線程池中的線程,至於是新建還是使用已有線程這個對用戶是完全透明的,也可以通過重載函數的參數傳入自定義的TaskScheduler。

Task任務狀態:

 狀態& 枚舉值  說明
 Created = 0  The task has been initialized but has not yet been scheduled
 WaitingForActivation = 1  The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure.
 WaitingToRun = 2  The task has been scheduled for execution but has not yet begun executing.
 Running = 3  The task is running but has not yet completed.
 WaitingForChildrenToComplete = 4  The task has finished executing and is implicitly waiting for attached child tasks to complete.
 RanToCompletion = 5  The task completed execution successfully
Canceled = 6  The task acknowledged cancellation by throwing an OperationCanceledException with its own CancellationToken while the token was in signaled state, or the task's CancellationToken was already signaled before the task started executing
 Faulted = 7  The task completed due to an unhandled exception

明確Task和線程的關系:

  • 任務是架構在線程之上的,也就是說任務最終還是要拋給線程去執行
  • 任務跟線程不是一對一的關系,比如開10個任務並不是說會開10個線程,在.NET面向任務異步編程模型中,你只需要關注業務概念的任務,具備底層實現會由Task包裝完成。
Task相比ThreadPool的優勢:
  • ThreadPool不支持線程取消、完成、失敗通知等交互新操作
  • ThreadPool不支持線程執行的先后順序。

 

await/async 語法糖

在異步編程實踐中,將網絡、數據庫同步訪問稱為 I/O-bound;將等待CPU計算結果稱為CPU-bound

  TAP異步編程模型的核心是塑造異步操作的Task、Task<T>對象,這是awaitable 對象,await/async語法糖簡化了寫法

  • 對於I/O-bound 代碼,編寫一個返回Task或Task<T>的async方法, 之后await 這個方法

  • 對於CPU-bound代碼,使用Task.Run方法后台啟動一個操作,之后await這個操作。

  魔法發生在await關鍵字,會將控制權上交給執行Async方法的上層調用者。

  在C#語言底層,編譯器將你的await/async 代碼轉換為狀態機, 記錄了當await發生時控制權上交后台工作完成時恢復執行的標記。

 
  異步編程在實踐時需要理解:
  • 異步代碼可用於I/O -bound 和CPU-bound 代碼, 但是2個場景的寫法是不同的

  • 異步編程利用Task和Task<T>對象來 塑造需要在后台完成的工作

  • async關鍵字將方法轉變為異步方法,這樣可在方法體使用await關鍵詞, 如果async方法內不包含await關鍵詞,那將不會上交控制權

  • 當await動作發生時,將會暫停(注意是suspend 而不是block)方法,並將控制權上交給調用者(直到awaitable任務完成)

  • await 只能被用在async方法內部

執行操作的“異步方式”

 執行以下操作…  替換以下方式…  使用以下方式
 檢索后台任務的結果  Task.Wait / Task.Result  await
 等待任何任務完成  Task.WaitAny  await Task.WhenAny
 檢索多個任務的結果  Task.WaitAll  await Task.WhenAll
 等待一段時間  Thread.Sleep  await Task.Delay


下面是一個I/O-bound的例子:    

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
namespace Test
{
    class Program
    {
        static void Main(string[] args)
       {
         var asyncMethod =  AccessTheWebAsync();
         Console.WriteLine("go on ...... "+ Thread.CurrentThread.ManagedThreadId );
         // 等待異步線程處理完畢,沒有以下句子,await使控制回到調用方,主線程即終止。
         asyncMethod.Wait();
       }
      public static async Task<int> AccessTheWebAsync()
      {
          HttpClient client = new HttpClient();
          // GetStringAsync returns a Task<string>.
          // That means that when you await the task you'll get a string (urlContents).
          Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
          // You can do work here that doesn't rely on the string from GetStringAsync.
         DoIndependentWork();

        // The await operator suspends AccessTheWebAsync.
        // - AccessTheWebAsync can't continue until getStringTask is complete.
        // - Meanwhile, control returns to the caller of AccessTheWebAsync.
        // - Control resumes here when getStringTask is complete.
        // - The await operator then retrieves the string result from getStringTask.
    
         string urlContents = await getStringTask;
         Console.WriteLine(urlContents.Length+"....... "+Thread.CurrentThread.ManagedThreadId );
         // The return statement specifies an integer result.
         // Any methods that are awaiting AccessTheWebAsync retrieve the length value.
         return urlContents.Length;
       } 
        public static void DoIndependentWork()
        {
            Console.WriteLine("work ......"+Thread.CurrentThread.ManagedThreadId);
        }
    }
}

  以上代碼在ASP.NET 或GUI程序可能會發生死鎖, 具體參見《.NET異步編程系列3:掌握SynchronizationContext避免deadlock》;控制台程序經過驗證在.NET Core 和.Net Framework上都沒有SynchronizationContext,故不會發生死鎖。

Task對象提供了豐富的API幫助我們完成 基於任務的異步操作, 讓我們專注業務概念的任務

 

作者: JulianHuang

感謝您的認真閱讀,如有問題請大膽斧正,如果您覺得本文對你有用,不妨右下角點個或加關注。

本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置注明本文的作者及原文鏈接,否則保留追究法律責任的權利。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM