寫在前面
在學異步,有位園友推薦了《async in C#5.0》,沒找到中文版,恰巧也想提升下英文,用我拙劣的英文翻譯一些重要的部分,純屬娛樂,簡單分享,保持學習,謹記謙虛。html
若是你以爲這件事兒沒意義翻譯的又差,盡情的踩吧。若是你以爲值得鼓勵,感謝留下你的贊,願愛技術的園友們在從此每一次應該猛烈突破的時候,不選擇知難而退。在每一次應該獨立思考的時候,不選擇隨波逐流,應該盡心盡力的時候,不選擇盡力而爲,不辜負每一秒存在的意義。web
轉載和爬蟲請註明原文連接http://www.cnblogs.com/tdws/p/5645075.html,博客園 蝸牛 2016年6月27日。express
目錄
編寫Async方法
如今咱們已經知道異步代碼有多棒了,可是它到底難寫嗎?是時候來看一看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的。若是能夠,打開原版的代碼,在你向下閱讀前,嘗試將它轉換成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究竟作了什麼。