在學異步,有位園友推薦了《async in C#5.0》,沒找到中文版,恰巧也想提高下英文,用我拙劣的英文翻譯一些重要的部分,純屬娛樂,簡單分享,保持學習,謹記謙虛。
如果你覺得這件事兒沒意義翻譯的又差,盡情的踩吧。如果你覺得值得鼓勵,感謝留下你的贊,願愛技術的園友們在今后每一次應該猛烈突破的時候,不選擇知難而退。在每一次應該獨立思考的時候,不選擇隨波逐流,應該全力以赴的時候,不選擇盡力而為,不辜負每一秒存在的意義。
轉載和爬蟲請注明原文鏈接http://www.cnblogs.com/tdws/p/5645075.html,博客園 蝸牛 2016年6月27日。
現在我們已經知道異步代碼有多棒了,但是它到底難寫嗎?是時候來看一看C#5.0的Async功能了。正如我們之前在第三章所看到的,一個async方法允許包含await關鍵字。
private asyncvoid DumpWebPageAsync(string uri) { WebClient webClient = new WebClient(); string page = awaitwebClient.DownloadStringTaskAsync(uri); Console.WriteLine(page); }
由於await關鍵字在此方法中,到await這里就不再繼續向下執行,直到下載結束恢復處理。這種處理使此方法異步,在本章,我們將會探索這樣的異步方法。
我們現在將之前那個示例轉換成Async的。如果可以,打開原版的代碼,在你向下閱讀前,嘗試將它轉換成async和await的方法。
最重要的方法是AddFavicon,即,將下載后的icons添加到UI界面上的方法。我們想把它編程異步的,這樣UI線程在下載期間就有空閑去相應用戶的操作。第一步要做的就是添加async關鍵字到方法上。它和static關鍵字在一樣的簽名位置。
然后我們需要使用await等待下載。await在C#語法中扮演者醫院運算符的角色,就像‘!’或者‘(type)轉換操作符’。他被放置在一個表達式的左側,意於異步的等待表達式。
最后,調用DownloadData方法必須替換成調用異步版本DownloadDataAsync。
Async方法不是自動做到異步的。Async方法僅僅是將調用(消耗consume)其它異步方法更加容易。他們同步地運行着,一直到調用異步方法和await它。當他們做這樣的事情時,必須使自身變得異步。有時,一個async方法不await任何事情。
private asyncvoid AddAFavicon(string domain) { WebClient webClient = new WebClient(); byte[] bytes = awaitwebClient.DownloadDataTaskAsync("http://" + domain + "/ favicon.ico"); Image imageControl = MakeImageControl(bytes); m_WrapPanel.Children.Add(imageControl); }
比較一下這種方式和之前章節所介紹的版本。這看起來更像同步代碼的樣子。沒有任何額外的方法,只在相同結構下有一點額外的代碼。然而,他的行為和我們在上一章中的其中一小節(點擊跳轉)所寫的版本很相像。
讓我們來分解一下我們寫的await吧。下面是WebClient.DownloadStringTaskAsync方法。
Task<string> DownloadStringTaskAsync(string address)
它的返回類型是Task<string>。就像我在介紹Task這一小節的介紹,Task代表一個執行中的操作。並且它的子類Task<T>代表着一個在將來某一時刻返回T類型的結果的操作。你可以認為Task<T>承諾返回T類型的值在這個耗時操作之后。
Task和Task<T>都可以代表異步操作,並且都有能力在操作完成后進行回調。在手動實現的異步方式中,你使用ContinueWith方法,傳遞一個委托,讓代碼在耗時操作結束后繼續下一步操作。await使用相同的方式執行你的剩余的代碼(也就是await之后的代碼)。
如果你對Task<T>運用await,他成為了一個await expression,並且整個表達式都擁有T類型。這意味着你可以等待一個變量的結果,並且可以在剩余的后半部分方法中使用,就像我們在例子中所看到的。然而當你await一個非泛型Task時,它保持await狀態,但不能被分配給任何東西,就像調用一個void方法。這意味着,作為一個Task不承諾返回任何值,他僅僅表示操作本身。
await smtpClient.SendMailAsync(mailMessage);
沒有什么可以把我們await表達式內部分開,所以我們可以直接的訪問Task,或者在等待中做一些其他事情。具體看下代碼和注釋你就明白了。
Task<string> myTask = webClient.DownloadStringTaskAsync(uri); // Do something here string page = await myTask;
完全理解它帶來的啟示很重要。DownloadStringTaskAsync方法在第一行執行,他開始在當前線程異步的執行,並且一旦開始了下載,它返回一個Task<string>(對照上面的代碼理解),依然在當前線程。只是在后來我們await Task<string>時,編譯器做了一些特別的事情。如果你把await寫在和調用異步方法在一行代碼里它一直是正確的。譯者解釋:也就是說如果調用await方法,在執行await內部操作的時候,這個線程是當前線程。執行await后的操作,可能是當前線程來處理后面的代碼,也可能是新的線程來處理后面的代碼。換種方式說,await所等待的方法,被當前線程來執行,但是執行時候立馬回收到線程池,下一步操作隨機選擇一個線程來執行。因此我認為所有認為await會開新線程的說法是錯誤的。再強調一次,await后之所以會出現新的線程,是因為執行await內部的線程被回收到池子中,從線程池中再取出一個來執行下面的代碼。await根本就沒有開啟線程的功能。
一旦調用DownloadStringTaskAsync發生,耗時操作開始執行,這同時給了我們一個很簡單的方法來執行多個異步操作。我們可以開始多個操作,保持Tasks,然后await他們。
Task<string> firstTask = webClient1.DownloadStringTaskAsync("http://oreilly.com"); Task<string> secondTask = webClient2.DownloadStringTaskAsync("http://simple-talk.com"); string firstPage = await firstTask; string secondPage = await secondTask;
等待多個Task是一種危險的方式,也許他們會拋出異常。如果兩個操作拋出一個異常,第一個await將會傳播它的異常,這意味着secondTask永遠不會被等待。它的異常可能不會被注意到,還取決於.NET版本和設置,也許會丟失或者在另一個非預期線程中拋出,還可能終止該進程。我們將會在第七章講到更好的方式去處理。
標記為async的方法有三種返回類型:
·void
·Task
·Task<T>
沒有其他允許的返回類型,因為通常再返回時方法都沒執行結束。通常情況下,異步方法將會await一個耗時操作,意思是方法將會迅速返回,但是卻在未來實現結果。也意味着,在方法返回時沒有明確的結果,而是遲一些才會變得可用。
我會展示方法返回值之間的區別—例如,Task<string>—這個返回類型,即編程人員打算返回給調用者的,在此情況下是string類型。通常,在非異步的方法中,返回類型和結果類型是一樣的。但他們之間這樣的不同對async方法很重要。
很明顯void返回類型是很合理的選擇在異步編程情況中。一個async void方法是一個“觸發並忘記”的異步操作。調用者不能等待任何返回結果,並且不能知道操作什么時候結束或者是否成功。當你確定你不需要知道操作何時結束或者是否成功時,你應該使用void。async void最常見的應用場景是在async代碼和其他代碼的邊界情況,比如UI事件處理必須返回void。
返回Task的異步方法允許調用者等待操作結束的結果,並且傳遞在異步代碼執行期間的異常。當我們不需要任何返回類值時,一個async Task方法比async void方法更好,因為他允許調用者使用await去等待,並且處理異常更容易。(前面已經說到void最適合的情況)。
最后,返回Task<T>的異步方法,像Task<string>,通常用於異步操作需要返回值的時候。
async關鍵字出現在方法的聲明上,就像public和static一樣。盡管如此,async不能用於方法的簽名,無論是重寫方法,實現接口還是被調時。
async關鍵字唯一的影響是在他所應用的方法內部編譯,而不像其他關鍵字,決定其如何與外界交互。正因如此,在關於重寫方法,定義接口的規則上完全不被理會。
class BaseClass { public virtual async Task<int> AlexsMethod() { ... } } class SubClass : BaseClass { // This overrides AlexsMethod above public override Task<int> AlexsMethod() { ... } }
接口不能使用async定義,很簡單,因為沒必要。如果一個借口需要方法返回Task,在實現時可以使用async,但是用不用還是方法自己的事兒。接口不需要特別聲明出是否要異步。
Return Statement在異步方法中有着不同的行為。想想在普通的非異步方法,使用return statement依賴於方法的返回類型。
void方法
return statement只需要return;,並且是可選擇。(不寫也行)
返回一個T類型的方法
return必須有一個T類型的表達式,比如5+x,並且必須出現在方法所有路徑的最后。
在一個標記為async的方法中,不同的情況也有不同的規則
void方法和返回Task的方法
return statement只需要return;,並且是可選擇的。(不寫也行)
返回Task<T>的方法
return必須返回一個T的表達式並且要在所有返回路徑的最后。
在異步方法中,方法的返回類型和表達式類型有所不同。編譯器轉換可以被認為是將你的結果值包裹起來,在返回給調用者之前。當然,事實上Task>T<立即被創建,並且一旦在你的耗時操作結束后,將你的值“填充”上。譯者:像前幾章講的一樣,異步方法立即返回被“包裹”的值,在執行結束后,填充值。
正如我們所見,最好的使用由異步返回的Task的方式是在異步方法中await它。當你這樣做時,你的方法通常也返回Task。為了享受異步風格的優勢,你調用方法的代碼必須不是阻塞地等待你的Task結束,並且這樣的話,你的調用者很可能也在await你。
下面示例是一個我曾經寫過的方法,用於讀取一個網頁中有多少個字符,並且異步的返回它們。
private async Task<int> GetPageSizeAsync(string url) { WebClient webClient = new WebClient(); string page = await webClient.DownloadStringTaskAsync(url); return page.Length; }
To use it, I need to write another async method, which returns its result asynchronously為了使用它,我需要寫另一個異步的返回自己結果的異步方法,就像這樣:
private async Task<string> FindLargestWebPage(string[] urls) { string largest = null; int largestSize = 0; foreach (string url in urls) { int size = await GetPageSizeAsync(url); if (size > largestSize) { size = largestSize; largest = url; } } return largest; }
在這種方式下,我們不用寫異步的方法鏈,只是每次await就好。Async是一個傳染性的編程模型,他可以很容易就彌漫到整個代碼體系。但是我認為這正是由於async方法如此容易的書寫,這完全沒有問題。
普通的命名方法可以異步,並且有兩種匿名方法一樣可以異步。語法和正常的方法也很像。下面是如何使用異步匿名委托的示例:
Func<Task<int>> getNumberAsync = async delegate { return 3; };
下面是async lambda:
Func<Task<string>> getWordAsync = async () => "hello";
和普通的異步代碼規則沒什么不一樣。你可以用他們來保持代碼清晰整潔,捕捉閉合,和非異步方法以完全相同的形式書寫。
最近好迷茫,可能有點太急躁,總覺得高不成低不就,拼命地想越走越高,又看不到自己明顯的進步。痛苦。
下一章節將介紹 await究竟做了什么。