寫在前面
在學異步,有位園友推薦了《async in C#5.0》,沒找到中文版,恰巧也想提升下英文,用我拙劣的英文翻譯一些重要的部分,純屬娛樂,簡單分享,保持學習,謹記謙虛。html
若是你以爲這件事兒沒意義翻譯的又差,盡情的踩吧。若是你以爲值得鼓勵,感謝留下你的贊,願愛技術的園友們在從此每一次應該猛烈突破的時候,不選擇知難而退。在每一次應該獨立思考的時候,不選擇隨波逐流,應該盡心盡力的時候,不選擇盡力而爲,不辜負每一秒存在的意義。web
轉載和爬蟲請註明原文連接http://www.cnblogs.com/tdws/p/5679001.html,博客園 蝸牛 2016年6月27日。編程
目錄
以Task爲基礎的異步模式
基於Task的異步編程模式(TAP)是Microsoft爲.Net平臺下使用Task進行編程所提供的一組建議和文檔—地址(譯者:後續也會翻譯此文檔,寫的確實不錯):http://www.microsoft.com/en-gb/download/details.aspx?id=19957。微軟並行編程團隊的Stephen Toub在文檔中提供了好的例子,很值得一讀。
這種模式提供了能夠被await消耗(調用)方法的APIs,而且當使用async關鍵字編寫遵照這種模式的方法時,手寫Task一般頗有用。在本章,我將介紹如何使用這種模式和技術。
TAP具體指什麼?
我假設咱們已經知道如何使用C#設計一個好的異步方法:
·它應該有儘可能少的參數,甚至不要參數。若是可能的話必定要避免ref和out參數。
·若是有意義的話,他應該有一個返回類型,他能真正的表達方法代碼的結果,而不是像C++那種成功標識。
·它應該有一個能夠解釋本身行爲的命名,而不依賴於額外的符號或註釋。
預期內的錯誤應該做爲返回類型的一部分,而非預期內的則應拋出異常。
這裏有一個DNS類下的,設計的不錯的異步方法:
public static IPHostEntry GetHostEntry(string hostNameOrAddress)
TAP提供了設計異步方法相同的準則,基於你已經掌握的異步方法技能。以下:
·他應該和異步方法有相同的參數,ref和out參數必定要避免。
·他應該返回Task或者Task<T>,固然這取決你你的方法是否有返回類型。這個任務應該在未來的某個時刻完成,並提供結果。
·他應該被命名爲NameAsync,Name等價於你表示做用的異步方法名字。
·因爲方法運行中咱們錯誤(預期外)的致使的異常應該被直接拋出。任何其餘異常(預期內)應該由Task來帶出。
下面是一個好的TAP方法設計:
public static Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress)
這一切彷佛很是明顯,可是像咱們在以前講過的.NET異步編程的幾種模式http://www.cnblogs.com/tdws/p/5628538.html#one,這是第三種正是在.NET框架下應用的異步模式,而且我肯定有無數的非正式的方式來寫異步代碼。
TAP的關鍵理念是異步方法返回Task,即封裝了未來完成耗時操做的結果。若是沒有這個理念,咱們過去的 異步模式不是要給方法增長額外參數,就是要增長額外方法或者事件來支撐回調機制。Task能夠包含任何回調所須要的基礎內容,而不須要以往雜亂的細節來污染你的方法,形成閱讀和書寫困難。
額外的好處是,因爲異步回調的機制如今在Task中,在異步調用時你不須要處處複製和準備回調方法。反過來這意味着這種機制可以承擔更加複雜強大的任務,使其可以作一些可行的事兒像恢復上下文,包括同步上下文。它也提供了一個通用的API用於處理異步操做,使編譯器功能像async同樣合理,其餘模式則達不到這種效果。
使用Task來作計算密集型的操做
有時,一個耗時操做既不作網絡請求也不訪問磁盤;他只是在一個須要不少處理器時間的複雜運算上耗費了時間。固然,咱們不能期望作到這一點像網絡請求同樣不佔用線程。可是在程序的UI上,咱們依然但願避免UI凍結形成不響應。爲了解決這件事兒,咱們不得不返回UI線程來處理其餘事件,而且用一個不一樣的線程來作耗時計算。
Task提供了一種很簡單的方法來作這件事,而且你可使用await像其餘異步同樣,從而在計算完成是更新UI界面:(譯者註釋:Task.Run()能夠開啓一個新的後臺線程。)
Task t = Task.Run(() => MyLongComputation(a, b));
Task.Run方法使用了ThreadPool中的一個線程來執行你給的委託。在這種狀況下,我使用了一個lambda使其更加容易傳遞本地變量到計算當中。由此產生的Task當即開始,而且咱們能夠await這個Task:
await Task.Run(() => MyLongComputation(a, b));
這是一個在後臺線程工做的很簡單的方式。
好比你須要更多的控制,好比使用哪一個線程執行計算或者如何排隊。Task有一個靜態的叫作TaskFactory類型的Factory的屬性。它有一個StratNew方法能夠控制你計算的執行:
Task t = Task.Factory.StartNew(() => MyLongComputation(a, b),
cancellationToken,
TaskCreationOptions.LongRunning,
taskScheduler);
若是你在編寫一個包含大量計算密集型的方法的類庫,你也許h忽視了去給你的方法提供一個異步版本,便可以經過調用Task.Run來開始工做在後臺線程中的版本。這不是一個好主意,由於你的API調用者比你更瞭解應用程序的線程需求。舉個例子。在web應用中,使用線程池沒有好處;惟一應該優化的是線程總數。Task.Run是一個很簡單的調用,因此若是須要的話就給調用者留下API以來調用吧。
建立一個可控的Task
TAP真的很容易被消費(調用),因此你能夠在你全部的接口中很容易的提供TAP模式。咱們已經知道在消費其餘TAP API時如何作,也知道如何使用使用異步方法。可是當耗時操做沒有可用的TAP API會怎樣呢?也許這是一個使用其餘異步模式的API,也許你沒有在消費(調用)一個API而是在作一些徹底手動的異步。
這裏咱們使用的工具是TaskCompletionSource<T>這是一種受你控制建立Task的方式。你可使Task在任何你想要的時候完成,你也能夠在任何地方給它一個異常讓它失敗。
咱們來看看這個例子。假設你想經過下面這個方法封裝一個提示展現給用戶:
Task<bool> GetUserPermission()
這封裝的是一個提示用戶是否贊成的自定義對話框。由於用戶的許可在你程序裏不少的地方須要,定義一個容易調用的方法是很重要的。這是一個很棒的地方來使用異步方法,由於你必定想釋放UI線程來實現展現對話框。可是它也不接近傳統的用於網絡請求和其餘耗時操做的異步方法。在這裏,咱們是等待用戶,咱們來看看這個方法的內部。
private Task<bool> GetUserPermission()
{
// Make a TaskCompletionSource so we can return a puppet Task
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
// Create the dialog ready
PermissionDialog dialog = new PermissionDialog();
// When the user is finished with the dialog, complete the Task using SetResult
dialog.Closed += delegate { tcs.SetResult(dialog.PermissionGranted); };
// Show the dialog
dialog.Show();
// Return the puppet Task, which isn't completed yet
return tcs.Task;
}
注意這個方法沒有被標記爲async;咱們手動的建立了一個Task,因此咱們不但願編譯器爲咱們建立一個。TaskCompletionSource<T>建立了這個Task,而且將它做爲一個屬性來返回,咱們以後可使用SetResult方法在TaskCompletionSource上使得該Task完成。
因爲咱們遵照了TAP,咱們的調用者就可使用await來等待用戶的許可,這個調用很天然。
if (await GetUserPermission())
{ ...
有一個煩惱就是TaskCompletionSource<T>沒有一個非泛型版本。然而因爲Task<T>是Task的父類,你能夠在任意你須要Task的地方使用Task<T>。反過來意味着你可使用一個TaskCompletionSource<T>,而且由一個Task做爲屬性所返回的Task<T>是徹底有效的。我每每使用一個TaskCompletionSource<Object>而且調用SetResult(null)來完成它。你能夠很容易個建立一個非泛型TaskCompletionSource若是你須要的話,能夠基於一個泛型的(譯者:把泛型的做爲父類)。
與舊的異步模式相互做用
.NET團隊在框架的全部重要的異步編程API上建立了TAP模式的版本。但頗有趣的是去理解如何把一個非TAP模式的異步代碼構建成TAP的,在這樣的狀況下,你須要和已有的異步代碼相互做用。下面有一個如何使用TaskCompletionSource<T>的有趣的例子。
咱們來檢查一下以前使用DNS例子的方法。在.NET4.0中,這個DNS方法使用的異步版本是IAsyncResult模式。這意味着它有Begin方法和End方法組成:
IAsyncResult BeginGetHostEntry(string hostNameOrAddress,
AsyncCallback requestCallback,
object stateObject)
IPHostEntry EndGetHostEntry(IAsyncResult asyncResult)
一般狀況,你也許消費(調用)這個API經過使用一個lambda做爲回調,而且在其中咱們要調用End方法。咱們要在此作的很明確,值使用一個TaskCompletionSource<T>去完成一個Task。
public static Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress)
{
TaskCompletionSource<IPHostEntry> tcs = new TaskCompletionSource<IPHostEntry>();
Dns.BeginGetHostEntry(hostNameOrAddress, asyncResult =>
{
try
{
IPHostEntry result = Dns.EndGetHostEntry(asyncResult);
tcs.SetResult(result);
}
catch (Exception e)
{
tcs.SetException(e);
}
}, null);
return tcs.Task;
}
這段代碼被可能的異常變得更復雜。若是該DNS方法失敗,在咱們調用EndGetHostEntry方法時會拋出異常。這就是爲何IAsyncResult模式在End方法中使用了一個比僅僅傳遞結果到回調方法中更使人費解的系統。當異常被拋出時,咱們應該把它裝載到咱們的TaskCompletionSource<T>當中,以便咱們的調用者能夠經過TAP的形式拿到異常。
實際上,咱們有足夠的API遵循這種模式,像.NET框架團隊編寫了一個工具方法,能夠將它們轉換成TAP版本,好比下面這樣:
Task t = Task<IPHostEntry>.Factory.FromAsync<string>(Dns.BeginGetHostEntry,
Dns.EndGetHostEntry,
hostNameOrAddress,
null);
它將Begin和End方法做爲委託,而且使用的機制和咱們之前的很像。但它可能比咱們簡單的方法更高效。
冷Task和熱Task
在.NET4.0中,任務並行庫最初提成了Task類型,他有一個 cold Task的概念,這還是須要開始的,與其相對的hot Task,則是已經在運行的。目前爲止,咱們僅處理了hot Task。
TAP明確指出全部Task在從方法返回前必須是hot的。幸運的是,咱們以前所講的全部技術中建立的Task都是hot Task。例外的是TaskCompletionSource<T>,它實際上沒有什麼cold和hot Task的概念。只須要確保在將來某個時刻完成可該Task。
前期工做
咱們已經知道當你調用一個TAP的異步方法,和其餘任何方法同樣,這個方法在當前線程中運行。不一樣點在於TAP方法在返回前沒有真正的完成工做。他會當即返回一個Task,而且Task將會在實際工做結束後完成。
咱們已經說過,一些代碼將會在方法中同步的運行,而且在當前線程。在這種狀況下的異步方法,至少代碼可達,而且包括操做數,第一個await,正如咱們在「異步方法直到被須要前是同步的」所講到的。
TAP建議經過TAP方法所作的同步工做應儘量最少數量。你能夠檢查參數是否有效,也能夠經過掃描一個緩存來避免耗時操做,而且你也不該該在其中作一個緩慢的計算。混合的方法,即作一些運算,接着作一些網絡請求或相似的事情是很好的辦法,可是你應該使用Task.Run將該計算移到後臺線程。想象一下常規的功能上傳圖片到網站上,但須要首先調整大小來節省帶寬:
Image resized = await Task.Run(() => ResizeImage(originalImage));
await UploadImage(resized);
這在UI app上是很重要的,這對於web app沒有什麼特別的好處。當咱們看見一個遵照TAP模式的方法,咱們但願他迅速返回。任何使用你代碼的人,並將其移動到UI app中,若是你的圖片調整很是緩慢,將會是一個「驚喜」
寫在最後
這周更新的有點慢。須要英文原著的能夠私信或留言。若是有錯誤,但願能指出。下一篇將介紹:異步代碼的一些工具方法