在學異步,有位園友推薦了《async in C#5.0》,沒找到中文版,恰巧也想提高下英文,用我拙劣的英文翻譯一些重要的部分,純屬娛樂,簡單分享,保持學習,謹記謙虛。
如果你覺得這件事兒沒意義翻譯的又差,盡情的踩吧。如果你覺得值得鼓勵,感謝留下你的贊,願愛技術的園友們在今后每一次應該猛烈突破的時候,不選擇知難而退。在每一次應該獨立思考的時候,不選擇隨波逐流,應該全力以赴的時候,不選擇盡力而為,不辜負每一秒存在的意義。
轉載和爬蟲請注明原文鏈接http://www.cnblogs.com/tdws/p/5679001.html,博客園 蝸牛 2016年6月27日。
基於Task的異步編程模式(TAP)是Microsoft為.Net平台下使用Task進行編程所提供的一組建議和文檔—地址(譯者:后續也會翻譯此文檔,寫的確實不錯):http://www.microsoft.com/en-gb/download/details.aspx?id=19957。微軟並行編程團隊的Stephen Toub在文檔中提供了好的例子,很值得一讀。
這種模式提供了可以被await消耗(調用)方法的APIs,並且當使用async關鍵字編寫遵守這種模式的方法時,手寫Task通常很有用。在本章,我將介紹如何使用這種模式和技術。
我假設我們已經知道如何使用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一樣合理,其他模式則達不到這種效果。
有時,一個耗時操作既不做網絡請求也不訪問磁盤;他只是在一個需要很多處理器時間的復雜運算上耗費了時間。當然,我們不能指望做到這一點像網絡請求一樣不占用線程。但是在程序的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以來調用吧。
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方法作為委托,並且使用的機制和我們以前的很像。但它可能比我們簡單的方法更高效。
在.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中,如果你的圖片調整非常緩慢,將會是一個“驚喜”
這周更新的有點慢。需要英文原著的可以私信或留言。如果有錯誤,希望能指出。下一篇將介紹:異步代碼的一些工具方法