在即將到來的新的Windows Runtime(Windows 運(yùn)行時)中更根本地確定任何API都不會運(yùn)行超過50ms的時間。需要更長時間的操作將會由'kick off this operation'API來代替,不等待運(yùn)算結(jié)果就直接立刻返回。這樣做是因為微軟希望Windows8 Metro程序能夠在即時的觸控UI(用戶界面)上能夠“快速并且流動”,因為觸控操作上即使是微小的停頓相比于用鼠標(biāo)或者鍵盤來操作都會變得更加明顯。從UI的角度來說,這是一項很有幫助的設(shè)計方案。
但是從開發(fā)者的角度來說,它會使編程變得更加麻煩。當(dāng)我們讀取文件或者調(diào)用WCF服務(wù)時,我們通常希望能夠影響到結(jié)果。如果能夠保證讀取文件或者WCF服務(wù)返回時結(jié)果肯定可獲得的,我們由上而下地寫出容易理解和推理的代碼。
string url = ReadUrlFromFile(filename);
string contentOfUrl = HttpGetFromUrl(url);
MessageBox.Show(contentOfUrl);
這樣的API被叫做同步或者阻塞。同步API易于理解和使用,不過在你的程序內(nèi)部當(dāng)前線程沒有反應(yīng)時,API就無法控制你的代碼去做其它任務(wù),因為它還不能傳遞結(jié)果。
擁使用即時返回的'kick off' API的方式叫做異步或者無阻塞。使用異步API編程更加繁難,因為你不能即時將結(jié)果返回給變量來保證運(yùn)行:
string url = BeginReadUrlFromFile(filename); // Won't work -- file read hasn't completed when BeginRead returns
string contentOfUrl = BeginHttpGetFromUrl(url); // Ditto
MessageBox.Show(contentOfUrl);
相反,你不得不回調(diào)需要使用返回結(jié)果的代碼,直到它已經(jīng)準(zhǔn)備好了:
BeginReadUrlFromFile(filename, url => {
BeginHttpGetFromUrl(url, contentOfUrl => {
MessageBox.Show(contentOfUrl);
});
});
甚至于這樣一個簡單的例子都顯得相當(dāng)丑陋。實際上,異步代碼中需要更多的運(yùn)算,更復(fù)雜的回調(diào),邏輯條件,early exits以及錯誤處理,所以會更加難看。.NET框架中真正的異步API更加丑陋,到處都是 IAsyncResult對象和成對的EndXxx方法調(diào)用。
然而,如果我們希望能程序在Windows Runtime中運(yùn)行,這就是用戶所希望我們做的。
原來的解決方案:使用F#
F#背后聰明的人們想出來一個兩全其美的解決方案。F#有一個叫做異步工作流程的特色功能,它由很多塊異步引進(jìn)的代碼塊組成。在異步工作流程中,你可以通過使用一個很像同步的語法來調(diào)用異步方法:
async {
let! url = BeginReadUrlFromFile filename
let! contentOfUrl = BeginHttpGetFromUrl url
MessageBox.Show(contentOfUrl)
}
F#編譯器自動將這易于閱讀、理解的代碼轉(zhuǎn)變?yōu)榭膳碌幕卣{(diào)式等價物,這樣你就可以簡單地使用異步調(diào)用的響應(yīng)行為從上而下地編程。
新的解決方案:使用C# 5
C#背后也有同樣聰明的人,所以新的C#中也實現(xiàn)了這項功能。Visual Studio 11 beta中包含的下一版本的C#進(jìn)了兩個新關(guān)鍵字——"async" 和 "await"
關(guān)鍵字"async"表明使用的是異步調(diào)用方法。這對于調(diào)用者來說,理解它非常重要,因為這意味著方法會在它結(jié)束前返回——方法能夠在異步調(diào)用時中途放棄而直接返回給它的調(diào)用者。
關(guān)鍵字"await"表明我們希望保證自上而下的邏輯 異步調(diào)用 而不是手動編寫回調(diào)函數(shù)。下面是他們完美結(jié)合在一起的例子:
public async void ShowReferencedContent(string filename) {
string url = await BeginReadFromFile(filename);
string contentOfUrl = await BeginHttpGetFromUrl(url);
MessageBox.Show(contentOfUrl);
}
這樣比回調(diào)更方便讀、寫和檢查,但他們的作用完全相同。(實際上,這確實比回調(diào)更智能些,因為編譯器并不會因為厭煩而跳過錯誤狀況或者弄錯early exit邏輯又或者忽略線程錯誤。)
當(dāng)我們調(diào)用方法時發(fā)生了什么?首先是調(diào)用BeginReadFromFile方法,它提供了文件名和編譯器生成的回調(diào)。BeginReadFromFile迅速返回,但結(jié)果仍然不可得。所以結(jié)果仍然不能分配給URL變量——回調(diào)的一部分——然后方法退出,返回給調(diào)用者!調(diào)用函數(shù)重新運(yùn)行,并且保持它的代碼持續(xù)運(yùn)行,盡管被調(diào)用方法還沒有結(jié)束。
然后在晚點時候,文件系統(tǒng)完成了閱讀操作。這意味著結(jié)果現(xiàn)在是可獲得的,Runtime安排回調(diào)。這并不一定會立刻發(fā)生——具體的時間還依賴于同步的環(huán)境。回調(diào)函數(shù)運(yùn)行著,捆將URL變量和文件操作的結(jié)果綁定,然后調(diào)用BeginHttpGetFromUrl。它也會立刻返回,也就是說,方法會再一次退出。
最后,HTTP操作完成,回調(diào)函數(shù)第二次運(yùn)行。它將綁定Url變量的內(nèi)容和顯示結(jié)果的消息框如果(如果有的話)。
我會希望向調(diào)用者返回什么值?
Async methods can exit before they’ve finished. So if an async method wants to return a result, it has to recognise that it might return to the caller before that result is available. For this reason, an async method that returns a value has to have a return type of Task rather than a ‘proper’ value. A Task represents a chunk of work which will eventually deliver a value, so a caller can examine the returned Task to determine when the result becomes available. Here’s how an async method looks when returning a value:
異步方法能夠在結(jié)束前退出,所以,如果一個異步方法希望返回一個結(jié)果就不得不確認(rèn)它是否在得到結(jié)果前就返回給調(diào)用者。因此,一個返回值的異步方法不得不包含一個Task返回類型而不是一個“合適的”值。一個Task代表最終會傳遞值的很大一塊工作,所以調(diào)用者也能堅持返回的Task來確定什么時候會得到結(jié)果。下面是一個返回值的異步方法的樣子:
public static async Task<string> GetReferencedContent(string filename)
{
string url = await BeginReadFromFile(filename);
string contentOfUrl = await BeginHttpGetFromUrl(url);
return contentOfUrl;
}
注意:盡管返回類型是Task<string>,返回狀態(tài)接收的是一條字符串。再一次,編譯器來管理返回狀態(tài)產(chǎn)生一個Task。
現(xiàn)在調(diào)用者能夠直接調(diào)用GetReferencedContent方法或者等待字符串變?yōu)榭傻?,或者手動讓它等待,又或者使它提前結(jié)束——無論如何它都適合使用結(jié)果。
Async-friendly APIs
如果你習(xí)慣在.NET 4或者更早之前版本上使用異步編程,你會習(xí)慣成對地使用Begin和End方法,比如WebRequest.BeginGetResponse 和WebRequest.EndGetResponse。這在.NET4.5中依然存在,但它們不使用await關(guān)鍵字。(主要是因為BeginXxx方法需要在回調(diào)中使用確切的方法調(diào)用來得到結(jié)果,而且編譯器并不依賴EndXxx命名規(guī)范).NET4.5提供了返回Task對象的新方法,所以你可以調(diào)用WebRequest.GetResponseAsync來代替WebRequest.BeginGetResponse方法。下面是一個.NET4.5中使用異步API的一個實例:
private static async Task<string> GetContent(string url)
{
WebRequest wr = WebRequest.Create(url);
var response = await wr.GetResponseAsync();
using (var stm = response.GetResponseStream())
{
using (var reader = new StreamReader(stm))
{
var content = await reader.ReadToEndAsync();
return content;
}
}
}
這和使用 WebRequest.GetResponse() 和 TextReader.ReadToEnd()的同步代碼是如此相似,只需要在API名后加上Async并且在方法前加上"await"關(guān)鍵字就可以了,相信你很快就能掌握它。
新聞熱點
疑難解答
圖片精選