通過使用異步編程,可避免出現(xiàn)性能瓶頸,并提高應(yīng)用程序的整體響應(yīng)。然而,技術(shù)編寫異步應(yīng)用程序的傳統(tǒng)方法過于復(fù)雜,這使得異步程序難以編寫,調(diào)試和維護(hù)。
Visual Studio2012引入了一個簡單的開發(fā)方法,異步編程,我們可以充分利用.NET Framework 4.5 和 Windows Runtime中對異步的支持。這項(xiàng)復(fù)雜的工作將會交由編譯器來搞定,開發(fā)人員就像是在使用同步代碼來編寫應(yīng)用程序的邏輯結(jié)構(gòu),但其結(jié)果是,得到了所有異 步編程的優(yōu)點(diǎn),但只要付出一點(diǎn)點(diǎn)工作。
本主題簡要介紹何時以及如何使用異步編程,其中包括支持說明本主題的例子。由此下載VS2012.
異步提高響應(yīng)能力
異步性必不可少,因?yàn)楝F(xiàn)實(shí)中有很多潛在的,可阻塞應(yīng)用程序響應(yīng)的情況,如當(dāng)你的應(yīng)用程序訪問網(wǎng)絡(luò),文件系統(tǒng)等等。比如訪問Web資源,有時是緩慢的 或者是有延遲的。同步處理的話,如果有這樣的一個阻塞產(chǎn)生,那么整個應(yīng)用程序就必須等待。而在一個異步的過程中,應(yīng)用程序可以先繼續(xù)進(jìn)行其他不依賴于網(wǎng)絡(luò) 的工作,直到所有可能產(chǎn)生阻塞的任務(wù)完成后再處理這些任務(wù)。
下表顯示了典型的可以通過異步編程提高響應(yīng)的場景。列出的.NET Framework4.5和Windows Runtime的API包含支持異步編程的方法。
應(yīng)用領(lǐng)域 | 支持異步方法的API |
訪問Web | HttpClient , SyndicationClient |
使用文件 | StorageFile, StreamWriter, StreamReader,XmlReader |
使用圖像 | MediaCapture, BitmapEncoder, BitmapDecoder |
WCF程序開發(fā) | Synchronous and Asynchronous Operations |
使用sockets | Socket |
異步性已經(jīng)被證明對所有通過一個線程訪問UI,或是處理UI相關(guān)的活動的應(yīng)用都特別的有價值。如果在同步的應(yīng)用程序中任何一個處理過程被阻塞,那就意味著所有的東西都被阻塞了。你的應(yīng)用程將會停止響應(yīng),更糟糕的是你可能會得出這樣的結(jié)論,這只是等待并不是失敗。
當(dāng)你使用異步方法,應(yīng)用程序?qū)⒗^續(xù)響應(yīng)的UI。您可以調(diào)整大小或最小化窗口,例如,當(dāng)你不想等下去的時候,你可以關(guān)閉該應(yīng)用程序。
現(xiàn)在這種基于異步的方法在你設(shè)計(jì)異步操作時,就像一組可以選擇的自動變速器,也就是說,你可以得到所有之前異步編程的好處,而不必像之前那樣苦逼(太讓人興奮了)。
異步方法現(xiàn)在很容易編寫
Async和Await關(guān)鍵字是C#異步編程的核心。通過使用這兩個關(guān)鍵字,你可以使用.NET Framework或Windows Runtime的資源創(chuàng)建一個異步方法如同你創(chuàng)建一個同步的方法一樣容易。通過使用async和await定義的異步方法,這里被稱為異步方法。
下面的例子顯示了一個異步方法。代碼中的幾乎所有的東西你看起來應(yīng)該非常熟悉。注釋中描述了你為實(shí)現(xiàn)異步操作添加什么功能。
1 // 在簽名中三個要注意的事項(xiàng):
2 // - 該方法具有一個async修飾符.
3 // - 返回類型為 Task or Task<t>. (參考 "返回類型" 一節(jié).)
4 // 這里, 返回值是 Task<int> 因?yàn)榉祷氐氖且粋€整數(shù)類型.
5 // - 這個方法的名稱以 "Async" 結(jié)尾.
6 async Task<int> AccessTheWebAsync()
7 {
8 // 你需要添加System.Net.Http的引用來聲明client
9 HttpClient client = new HttpClient();
10
11 // GetStringAsync 返回 Task<string>. 這意味著當(dāng)Task結(jié)束等待之后
12 // 你將得到一個string (urlContents).
13 Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
14
15 // 你可以做一些不依賴于 GetStringAsync 返回值的操作.
16 DoIndependentWork();
17
18 // await 操作掛起了當(dāng)前方法AccessTheWebAsync.
19 // - AccessTheWebAsync 直到getStringTask完成后才會繼續(xù).
20 // - 同時, 控制權(quán)將返回 AccessTheWebAsync 的調(diào)用者.
21 // - 控制權(quán)會在getStringTask完成后歸還到AccessTheAsync.
22 // - await操作將取回getStringTask中返回的string結(jié)果.
23 string urlContents = await getStringTask;
24
25 // return語句用來指定一個整數(shù)結(jié)果。
26 // 調(diào)用AccessTheWebAsync將會收到一個返回值的長度.
27 return urlContents.Length;
28 }
如果AccessTheWebAsync沒有什么不依賴于GetStringAsync的內(nèi)容,也可以直接調(diào)用如下代碼:
string urlContents = await client.GetStringAsync();
以下幾個特點(diǎn)總結(jié)了一下前面的例子中的異步方法。
方法中包含了 async 修飾符。
一個async方法按照慣例以“Async”結(jié)尾。
返回類型是如下類型之一:
Task<TResult> 當(dāng)你的方法有返回值時,TResult即返回值的類型
Task 當(dāng)你的方法沒有return語句,或者返回值并不參與任何形式的運(yùn)算(包括賦值操作)。
Void 當(dāng)你編寫一個異步事件處理時會用到
方法通常包括至少一個await的表達(dá)式,這意味著該方法在遇到await時不能繼續(xù)執(zhí)行,直到等待異步操作完成。在此期間,該方法將被暫停,并且控制權(quán)返回到該方法的調(diào)用者。本主題接下來的部分說明了懸掛點(diǎn)后會發(fā)生什么。
在異步方法中,我們使用這些已經(jīng)提供的關(guān)鍵字和類型來表達(dá)想要做什么的時候,編譯器并沒有閑著,他將處理包括跟蹤在暫停方法中控制權(quán)返回到 await點(diǎn)后將會如何處理。一些常規(guī)流程,如循環(huán)和異常處理,在之前的異步代碼中都比較難以處理。而現(xiàn)在都?xì)w結(jié)到了一個async方法中,你會感覺你在 寫一個同步的代碼,之前的那些困惑已經(jīng)不復(fù)存在了。
在異步方法中發(fā)生了什么
了解異步編程最重要的是理解控制權(quán)是如何在方法之間轉(zhuǎn)移的。下面的圖標(biāo)將會解釋這個過程。
圖中的數(shù)字對應(yīng)于下面的步驟。
1. 一個事件的處理函數(shù)用await的方式調(diào)用了異步方法 AccessTheWebAsync。
2. AccessTheWebAsync 創(chuàng)建了一個 HttpClient 實(shí)例,并調(diào)用了異步方法 GetStringAsync 來以下載一個頁面并將內(nèi)容以string的形式返回.
3. 在GetStringAsync中將會碰到一些讓進(jìn)程掛起的事情。也許它必須等待一個網(wǎng)站下載完成或其他一些阻塞性的動作,為了避免阻塞資源,GetStringAsync把控制權(quán)移交給了它的調(diào)用者AccessTheWEbAsync。
GetStringAsync返回Task<TResult>泛型,例子中的TResult是string類 型,AccessTheWebAsync將任務(wù)交給了getStringTask變量。這個任務(wù)代表了一個正在調(diào)用GetStringAsync的過程, 與一個承諾,當(dāng)工作完成時,最終將產(chǎn)生string返回值。
4. 由于getStringTask并沒有被(用await)等待著索取結(jié)果,so AccessTheWebAsync 可以繼續(xù)其他不依賴于GetStringAsync最終返回結(jié)果的其他任務(wù)。這些任務(wù)由一個同步方法DoIndenpendentWork代表。
5.DoIndenpendentWork是一個同步方法,將會以同步的方式執(zhí)行他的工作,并將返回值返回給調(diào)用者。
6. AccessTheWebAsync下一步是希望計(jì)算已經(jīng)下載下來的字符長度,但是如果沒有這個字符,這個希望也就破滅了。
因此,AccessTheWebAsync 使用了await關(guān)鍵字掛起了自己的線程,并將控制權(quán)移交到了AccessTheWebAsync的調(diào)用者。AccessTheWebAsync將會返回 一個Task<int>給調(diào)用函數(shù)。這個任務(wù)承諾會在結(jié)束是返回給調(diào)用者一個int型的返回值。
注意
如果GetStringAsync在AccessTheWebAsync 調(diào)用await之前就已經(jīng)完成了,那么控制權(quán)將依然在AccessTheWebAsync中。這種掛起和等待的操作也是很消耗資源的,如果返回值在 await之前就已經(jīng)得到了,AccessTheWebAsync沒必要非得在用等待的方法去得到最終的結(jié)果。
在調(diào)用方法(這里是一個event的處理函數(shù))的內(nèi)部,處理過程是疊加的,event的處理函數(shù)將會等待AccessTheWebAsync,而 AccessTheWebAsync在等待GetStringAsync,與此同時,調(diào)用函數(shù)依然可以執(zhí)行不依賴于這些返回值的操作。
7. GetStringAsync計(jì)算并產(chǎn)生一個string結(jié)果。這個string結(jié)果可能不是按照你現(xiàn)在期望的方式直接返回給他的調(diào)用函數(shù),相反,這個結(jié) 果保存在一個代表方法完成的任務(wù)中,getStringTask。await操作符將會從getStringTask中取回期望的結(jié)果。賦值語句將會把結(jié) 果交給urlContents。
8. 當(dāng)AccessTheWebAsync獲得了這個字符串結(jié)果,我們可以繼續(xù)計(jì)算這個字符串的長度了。這樣AccessTheWebAsync的工作也完成了,等待中的event處理函數(shù)也可以繼續(xù)了。
如果你剛剛接觸異步編程,那應(yīng)該花一分鐘來思考一下同步行為和異步行為的不同。同步方法在工作完成之后返回(步驟5),但是異步方法返回一個 task值在他工作被暫停的時候(步驟3,6).當(dāng)異步方法完成了他的工作之后,task被標(biāo)記為complete,工作的結(jié)果也保存在task之中。
異步的API方法
你可能會想知道在哪里可以找到,如GetStringAsync,支持異步編程的API方法。.NET Framework 4.5包含許多使用await和async工作的成員方法。識別這些方法很簡單,方法名都是以”Async”結(jié)尾的并且返回類型都是Task或者 Task<TResult>。例如,System.IO.Stream類包含的方法,如CopyToAsync,ReadAsync,及 WriteAsync的對應(yīng)的同步方法是CopyTo,Read和Write。
線程
異步方法的目的是不阻塞操作。在async方法中, await任務(wù)在執(zhí)行的過程中,并不會阻塞當(dāng)前的線程,其余的方法可以繼續(xù)執(zhí)行,控制權(quán)將會移交到async方法的調(diào)用者。
async和await關(guān)鍵字并不會創(chuàng)建額外的線程,async方法不會去請求多線程操作。真正創(chuàng)建線程的操作是由Task.Run()實(shí)現(xiàn)的,一個async方法并不是在他自己的線程上執(zhí)行的,只有當(dāng)方法被激活時,才會使用當(dāng)前線程的上下文和處理時間。
async方法要比BackgroundWorker更實(shí)用,而且使用起來更簡單而且不用去過多的考慮競態(tài)沖突神馬的。async方法會將運(yùn)行中的代碼依據(jù)某些算法進(jìn)行合理的拆分,并傳遞給線程池,這也是BackgroundWorker不能比的。
Async和Await
如果需要使用async或者await指定一個異步方法,我們需要注意一下兩點(diǎn):
用async標(biāo)記的異步方應(yīng)該使用await關(guān)鍵子來制定掛起點(diǎn)。await操作符會告訴編譯器,這個async方放在完成之前,后面的代碼無法繼續(xù)執(zhí)行,同時,控制權(quán)轉(zhuǎn)移到async方法的調(diào)用者。
標(biāo)記為async的方法,調(diào)用時應(yīng)使用await。
一個async方法里通常包含一個或多個的對應(yīng)的await操作符,但如果沒有await表達(dá)式也不會導(dǎo)致編譯錯誤。但如果調(diào)用一個async方 法,卻不使用await關(guān)鍵字來標(biāo)記一個掛起點(diǎn)的話,程序?qū)雎詀sync關(guān)鍵字并以同步的方式執(zhí)行。編譯器會對類似的問題發(fā)出警告。
async和await都是上下文關(guān)鍵字:更多的細(xì)節(jié)可以參考:
Async (Visual Basic)
async (C# Reference)
Await Operator (Visual Basic)
await (C# Reference)
返回類型和參數(shù)
在.NET Framework編程中,一個async方法通常返回的類型是Task或者Task<TResult>。在異步方法中,await操作符作用于從另外一個異步方法返回的Task。
如果指定Task<TResult>為返回結(jié)果,那么這個方法必須包含return指定的TResult結(jié)果的語句。
如果使用Task作為返回值,那么這個方法應(yīng)該不存在使用return語句返回結(jié)果的代碼,或者返回的結(jié)果不參與任何運(yùn)算(包括賦值操作)。
1 // 明確指定 Task<tresult>
2 async Task<int> TaskOfTResult_MethodAsync()
3 {
4 int hours;
5 // . . .
6 // return一個整數(shù)作為結(jié)果.
7 return hours;
8 }
9
10 // 調(diào)用 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 // 調(diào)用 Task_MethodAsync
25 Task returnedTask = Task_MethodAsync();
26 await returnedTask;
27 // 或者使用一條語句
28 await Task_MethodAsync();
每一個返回的task都代表一個正在執(zhí)行的工作,task包裝的信息中包含了這個異步方法的執(zhí)行時的狀態(tài),最終的結(jié)果,或者處理過程中拋出的異常。
如果返回值為void,這種類型主要使用于定義事件處理。異步事件通常被認(rèn)為是一系列異步操作的開始。使用void返回類型不需要await,而且調(diào)用void異步方法的函數(shù)不會捕獲方法拋出的異常。
另外,async方法不能使用ref或者out參數(shù),但是可以調(diào)用含有這些參數(shù)的方法。
命名約定
按照約定,你應(yīng)該在異步方法的名稱后面追加“Async”用以標(biāo)記此方法。但是在event,基類和接口中不需要遵守約定,就像本文例子中event處理函數(shù)Button1_Click一樣。
相關(guān)主題
一個完整的例子
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處理函數(shù)用async標(biāo)記,這樣就可以在處理函數(shù)中使用await實(shí)現(xiàn)異步操作.
24 private async void StartButton_Click(object sender, RoutedEventArgs e)
25 {
26 // 調(diào)用和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 // 在簽名中三個要注意的事項(xiàng):
39 // - 該方法具有一個async修飾符.
40 // - 返回類型為 Task or Task<t>. (參考 "返回類型" 一節(jié).)
41 // 這里, 返回值是 Task<int> 因?yàn)榉祷氐氖且粋€整數(shù)類型.
42 // - 這個方法的名稱以 "Async" 結(jié)尾.
43 async Task<int> AccessTheWebAsync()
44 {
45 // 你需要添加System.Net.Http的引用來聲明client
46 HttpClient client = new HttpClient();
47
48 // GetStringAsync 返回 Task<string>. 這意味著當(dāng)Task結(jié)束等待之后
49 // 你將得到一個string (urlContents).
50 Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
51
52 // 你可以做一些不依賴于 GetStringAsync 返回值的操作.
53 DoIndependentWork();
54
55 // await 操作掛起了當(dāng)前方法AccessTheWebAsync.
56 // - AccessTheWebAsync 直到getStringTask完成后才會繼續(xù).
57 // - 同時, 控制權(quán)將返回 AccessTheWebAsync 的調(diào)用者.
58 // - 控制權(quán)會在getStringTask完成后歸還到AccessTheAsync.
59 // - await操作將取回getStringTask中返回的string結(jié)果.
60 string urlContents = await getStringTask;
61
62 // return語句用來指定一個整數(shù)結(jié)果。
63 // 調(diào)用AccessTheWebAsync將會收到一個返回值的長度.
64 return urlContents.Length;
65 }
66
67
68 void DoIndependentWork()
69 {
70 resultsTextBox.Text += "Working . . . . . . .\r\n";
71 }
72 }
73 }
74
75 // 運(yùn)行結(jié)果:
76
77 // Working . . . . . . .
78
79 // Length of the downloaded string: 41564.