介紹.net 4.5 異步編程核心內容,如何使用Async和Await進行異步編程


通過使用異步編程,可避免出現性能瓶頸,並提高應用程序的整體響應。然而,技術編寫異步應用程序的傳統方法過於復雜,這使得異步程序難以編寫,調試和維護。

Visual Studio2012引入了一個簡單的開發方法,異步編程,我們可以充分利用.NET Framework 4.5 和 Windows Runtime中對異步的支持。這項復雜的工作將會交由編譯器來搞定,開發人員就像是在使用同步代碼來編寫應用程序的邏輯結構,但其結果是,得到了所有異 步編程的優點,但只要付出一點點工作。

本主題簡要介紹何時以及如何使用異步編程,其中包括支持說明本主題的例子。由此下載VS2012.

異步提高響應能力

異步性必不可少,因為現實中有很多潛在的,可阻塞應用程序響應的情況,如當你的應用程序訪問網絡,文件系統等等。比如訪問Web資源,有時是緩慢的 或者是有延遲的。同步處理的話,如果有這樣的一個阻塞產生,那么整個應用程序就必須等待。而在一個異步的過程中,應用程序可以先繼續進行其他不依賴於網絡 的工作,直到所有可能產生阻塞的任務完成后再處理這些任務。

 

下表顯示了典型的可以通過異步編程提高響應的場景。列出的.NET Framework4.5和Windows Runtime的API包含支持異步編程的方法。

應用領域 支持異步方法的API
訪問Web

HttpClient , SyndicationClient

使用文件

StorageFile, StreamWriter, StreamReader, XmlReader

使用圖像

MediaCapture, BitmapEncoder, BitmapDecoder

WCF程序開發

Synchronous and Asynchronous Operations

使用sockets

Socket

異步性已經被證明對所有通過一個線程訪問UI,或是處理UI相關的活動的應用都特別的有價值。如果在同步的應用程序中任何一個處理過程被阻塞,那就意味着所有的東西都被阻塞了。你的應用程將會停止響應,更糟糕的是你可能會得出這樣的結論,這只是等待並不是失敗。

當你使用異步方法,應用程序將繼續響應的UI。您可以調整大小或最小化窗口,例如,當你不想等下去的時候,你可以關閉該應用程序。

現在這種基於異步的方法在你設計異步操作時,就像一組可以選擇的自動變速器,也就是說,你可以得到所有之前異步編程的好處,而不必像之前那樣苦逼(太讓人興奮了)。

異步方法現在很容易編寫

 

Async和Await關鍵字是C#異步編程的核心。通過使用這兩個關鍵字,你可以使用.NET Framework或Windows Runtime的資源創建一個異步方法如同你創建一個同步的方法一樣容易。通過使用async和await定義的異步方法,這里被稱為異步方法。

下面的例子顯示了一個異步方法。代碼中的幾乎所有的東西你看起來應該非常熟悉。注釋中描述了你為實現異步操作添加什么功能。

 1  //  在簽名中三個要注意的事項: 
 2  //   - 該方法具有一個async修飾符.  
 3  //   - 返回類型為 Task or Task<t>. (參考 "返回類型" 一節.)
 4  //     這里, 返回值是 Task<int> 因為返回的是一個整數類型. 
 5  //   - 這個方法的名稱以 "Async" 結尾.
 6 async Task< int> AccessTheWebAsync()
 7 { 
 8      //  你需要添加System.Net.Http的引用來聲明client
 9     HttpClient client =  new HttpClient();
10 
11      //  GetStringAsync 返回 Task<string>. 這意味着當Task結束等待之后 
12       //  你將得到一個string (urlContents).
13     Task< string> getStringTask = client.GetStringAsync( " http://msdn.microsoft.com ");
14 
15      //  你可以做一些不依賴於 GetStringAsync 返回值的操作.
16     DoIndependentWork();
17 
18      //  await 操作掛起了當前方法AccessTheWebAsync. 
19       //   - AccessTheWebAsync 直到getStringTask完成后才會繼續. 
20       //   - 同時, 控制權將返回 AccessTheWebAsync 的調用者. 
21       //   - 控制權會在getStringTask完成后歸還到AccessTheAsync.  
22       //   - await操作將取回getStringTask中返回的string結果. 
23      string urlContents = await getStringTask;
24 
25      //  return語句用來指定一個整數結果。
26       //  調用AccessTheWebAsync將會收到一個返回值的長度. 
27      return urlContents.Length;
28 }

 

如果AccessTheWebAsync沒有什么不依賴於GetStringAsync的內容,也可以直接調用如下代碼:

 

string urlContents = await client.GetStringAsync();

 

以下幾個特點總結了一下前面的例子中的異步方法。

  • 方法中包含了 async 修飾符。
  • 一個async方法按照慣例以“Async”結尾。
  • 返回類型是如下類型之一:
    • Task<TResult> 當你的方法有返回值時,TResult即返回值的類型
    • Task 當你的方法沒有return語句,或者返回值並不參與任何形式的運算(包括賦值操作)。
    • Void 當你編寫一個異步事件處理時會用到
  • 方法通常包括至少一個await的表達式,這意味着該方法在遇到await時不能繼續執行,直到等待異步操作完成。在此期間,該方法將被暫停,並且控制權返回到該方法的調用者。本主題接下來的部分說明了懸掛點后會發生什么。

在異步方法中,我們使用這些已經提供的關鍵字和類型來表達想要做什么的時候,編譯器並沒有閑着,他將處理包括跟蹤在暫停方法中控制權返回到 await點后將會如何處理。一些常規流程,如循環和異常處理,在之前的異步代碼中都比較難以處理。而現在都歸結到了一個async方法中,你會感覺你在 寫一個同步的代碼,之前的那些困惑已經不復存在了。

在異步方法中發生了什么

了解異步編程最重要的是理解控制權是如何在方法之間轉移的。下面的圖標將會解釋這個過程。

跟蹤一個異步程序 

圖中的數字對應於下面的步驟。

1. 一個事件的處理函數用await的方式調用了異步方法 AccessTheWebAsync。

2. AccessTheWebAsync 創建了一個 HttpClient 實例,並調用了異步方法 GetStringAsync 來以下載一個頁面並將內容以string的形式返回.

3. 在GetStringAsync中將會碰到一些讓進程掛起的事情。也許它必須等待一個網站下載完成或其他一些阻塞性的動作,為了避免阻塞資源,GetStringAsync把控制權移交給了它的調用者AccessTheWEbAsync。

GetStringAsync返回Task<TResult>泛型,例子中的TResult是string類 型,AccessTheWebAsync將任務交給了getStringTask變量。這個任務代表了一個正在調用GetStringAsync的過程, 與一個承諾,當工作完成時,最終將產生string返回值。

4. 由於getStringTask並沒有被(用await)等待着索取結果,so AccessTheWebAsync 可以繼續其他不依賴於GetStringAsync最終返回結果的其他任務。這些任務由一個同步方法DoIndenpendentWork代表。

5.DoIndenpendentWork是一個同步方法,將會以同步的方式執行他的工作,並將返回值返回給調用者。

6. AccessTheWebAsync下一步是希望計算已經下載下來的字符長度,但是如果沒有這個字符,這個希望也就破滅了。

因此,AccessTheWebAsync 使用了await關鍵字掛起了自己的線程,並將控制權移交到了AccessTheWebAsync的調用者。AccessTheWebAsync將會返回 一個Task<int>給調用函數。這個任務承諾會在結束是返回給調用者一個int型的返回值。

注意

如果GetStringAsync在AccessTheWebAsync 調用await之前就已經完成了,那么控制權將依然在AccessTheWebAsync中。這種掛起和等待的操作也是很消耗資源的,如果返回值在 await之前就已經得到了,AccessTheWebAsync沒必要非得在用等待的方法去得到最終的結果。

在調用方法(這里是一個event的處理函數)的內部,處理過程是疊加的,event的處理函數將會等待AccessTheWebAsync,而 AccessTheWebAsync在等待GetStringAsync,與此同時,調用函數依然可以執行不依賴於這些返回值的操作。

7. GetStringAsync計算並產生一個string結果。這個string結果可能不是按照你現在期望的方式直接返回給他的調用函數,相反,這個結 果保存在一個代表方法完成的任務中,getStringTask。await操作符將會從getStringTask中取回期望的結果。賦值語句將會把結 果交給urlContents。

8. 當AccessTheWebAsync獲得了這個字符串結果,我們可以繼續計算這個字符串的長度了。這樣AccessTheWebAsync的工作也完成了,等待中的event處理函數也可以繼續了。

如果你剛剛接觸異步編程,那應該花一分鍾來思考一下同步行為和異步行為的不同。同步方法在工作完成之后返回(步驟5),但是異步方法返回一個 task值在他工作被暫停的時候(步驟3,6).當異步方法完成了他的工作之后,task被標記為complete,工作的結果也保存在task之中。

異步的API方法

你可能會想知道在哪里可以找到,如GetStringAsync,支持異步編程的API方法。.NET Framework 4.5包含許多使用await和async工作的成員方法。識別這些方法很簡單,方法名都是以”Async”結尾的並且返回類型都是Task或者 Task<TResult>。例如,System.IO.Stream類包含的方法,如CopyToAsync,ReadAsync,及 WriteAsync的對應的同步方法是CopyTo,Read和Write。

線程

異步方法的目的是不阻塞操作。在async方法中, await任務在執行的過程中,並不會阻塞當前的線程,其余的方法可以繼續執行,控制權將會移交到async方法的調用者。

 

async和await關鍵字並不會創建額外的線程,async方法不會去請求多線程操作。真正創建線程的操作是由Task.Run()實現的,一個async方法並不是在他自己的線程上執行的,只有當方法被激活時,才會使用當前線程的上下文和處理時間。

 

async方法要比BackgroundWorker更實用,而且使用起來更簡單而且不用去過多的考慮競態沖突神馬的。async方法會將運行中的代碼依據某些算法進行合理的拆分,並傳遞給線程池,這也是BackgroundWorker不能比的。

Async和Await

 

如果需要使用async或者await指定一個異步方法,我們需要注意一下兩點:

  • 用async標記的異步方應該使用await關鍵子來制定掛起點。await操作符會告訴編譯器,這個async方放在完成之前,后面的代碼無法繼續執行,同時,控制權轉移到async方法的調用者。
  • 標記為async的方法,調用時應使用await。

一個async方法里通常包含一個或多個的對應的await操作符,但如果沒有await表達式也不會導致編譯錯誤。但如果調用一個async方 法,卻不使用await關鍵字來標記一個掛起點的話,程序將會忽略async關鍵字並以同步的方式執行。編譯器會對類似的問題發出警告。

 

async和await都是上下文關鍵字:更多的細節可以參考:

返回類型和參數

在.NET Framework編程中,一個async方法通常返回的類型是Task或者Task<TResult>。在異步方法中,await操作符作用於從另外一個異步方法返回的Task。

 

如果指定Task<TResult>為返回結果,那么這個方法必須包含return指定的TResult結果的語句。

如果使用Task作為返回值,那么這個方法應該不存在使用return語句返回結果的代碼,或者返回的結果不參與任何運算(包括賦值操作)。

 1  //  明確指定 Task<tresult>
 2 async Task< int> TaskOfTResult_MethodAsync()
 3 {
 4      int hours;
 5      //  . . .
 6       //  return一個整數作為結果.
 7      return hours;
 8 }
 9 
10  //  調用 TaskOfTResult_MethodAsync
11 Task< int> returnedTaskTResult = TaskOfTResult_MethodAsync();
12  int intResult = await returnedTaskTResult;
13  //  或者使用一條語句
14  int intResult = await TaskOfTResult_MethodAsync();
15 
16 
17  //  明確指定 Task
18 async Task Task_MethodAsync()
19 {
20      //  . . .
21       //  方法沒有任何return語句.  
22 }
23 
24  //  調用 Task_MethodAsync
25 Task returnedTask = Task_MethodAsync();
26 await returnedTask;
27  //  或者使用一條語句
28 await Task_MethodAsync();

 

每一個返回的task都代表一個正在執行的工作,task包裝的信息中包含了這個異步方法的執行時的狀態,最終的結果,或者處理過程中拋出的異常。

如果返回值為void,這種類型主要使用於定義事件處理。異步事件通常被認為是一系列異步操作的開始。使用void返回類型不需要await,而且調用void異步方法的函數不會捕獲方法拋出的異常。

另外,async方法不能使用ref或者out參數,但是可以調用含有這些參數的方法。

命名約定

按照約定,你應該在異步方法的名稱后面追加“Async”用以標記此方法。但是在event,基類和接口中不需要遵守約定,就像本文例子中event處理函數Button1_Click一樣。

相關主題

一個完整的例子

 

 1  using System;
 2  using System.Collections.Generic;
 3  using System.Linq;
 4  using System.Text;
 5  using System.Threading.Tasks;
 6  using System.Windows;
 7  using System.Windows.Controls;
 8  using System.Windows.Data;
 9  using System.Windows.Documents;
10  using System.Windows.Input;
11  using System.Windows.Media;
12  using System.Windows.Media.Imaging;
13  using System.Windows.Navigation;
14  using System.Windows.Shapes;
15 
16  
17  using System.Net.Http;
18 
19  namespace AsyncFirstExample
20 {
21      public  partial  class MainWindow : Window
22     {
23          //  將event處理函數用async標記,這樣就可以在處理函數中使用await實現異步操作. 
24          private async  void StartButton_Click( object sender, RoutedEventArgs e)
25         {
26              //  調用和await分離的方式. 
27               // Task<int> getLengthTask = AccessTheWebAsync(); 
28              /// / 在這里做一些其他的工作. 
29              // int contentLength = await getLengthTask; 
30 
31              int contentLength = await AccessTheWebAsync();
32 
33             resultsTextBox.Text +=
34                 String.Format( " \r\nLength of the downloaded string: {0}.\r\n ", contentLength);
35         }
36 
37 
38             //  在簽名中三個要注意的事項: 
39       //   - 該方法具有一個async修飾符.  
40       //   - 返回類型為 Task or Task<t>. (參考 "返回類型" 一節.)
41       //     這里, 返回值是 Task<int> 因為返回的是一個整數類型. 
42       //   - 這個方法的名稱以 "Async" 結尾.
43     async Task< int> AccessTheWebAsync()
44     { 
45              //  你需要添加System.Net.Http的引用來聲明client
46             HttpClient client =  new HttpClient();
47 
48              //  GetStringAsync 返回 Task<string>. 這意味着當Task結束等待之后 
49               //  你將得到一個string (urlContents).
50             Task< string> getStringTask = client.GetStringAsync( " http://msdn.microsoft.com ");
51 
52              //  你可以做一些不依賴於 GetStringAsync 返回值的操作.
53             DoIndependentWork();
54 
55              //  await 操作掛起了當前方法AccessTheWebAsync. 
56               //   - AccessTheWebAsync 直到getStringTask完成后才會繼續. 
57               //   - 同時, 控制權將返回 AccessTheWebAsync 的調用者. 
58               //   - 控制權會在getStringTask完成后歸還到AccessTheAsync.  
59               //   - await操作將取回getStringTask中返回的string結果. 
60              string urlContents = await getStringTask;
61 
62              //  return語句用來指定一個整數結果。
63               //  調用AccessTheWebAsync將會收到一個返回值的長度. 
64              return urlContents.Length;
65     }
66 
67 
68          void DoIndependentWork()
69         {
70             resultsTextBox.Text +=  " Working . . . . . . .\r\n ";
71         }
72     }
73 }
74 
75  //  運行結果: 
76 
77  //  Working . . . . . . . 
78 
79  //  Length of the downloaded string: 41564.

 相關主題


免責聲明!

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



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