異步編程系列第04章 編寫Async方法

寫在前面

  在學異步,有位園友推薦了《async in C#5.0》,沒找到中文版,恰巧也想提升下英文,用我拙劣的英文翻譯一些重要的部分,純屬娛樂,簡單分享,保持學習,謹記謙虛。html

  若是你以爲這件事兒沒意義翻譯的又差,盡情的踩吧。若是你以爲值得鼓勵,感謝留下你的贊,願愛技術的園友們在從此每一次應該猛烈突破的時候,不選擇知難而退。在每一次應該獨立思考的時候,不選擇隨波逐流,應該盡心盡力的時候,不選擇盡力而爲,不辜負每一秒存在的意義。web

   轉載和爬蟲請註明原文連接http://www.cnblogs.com/tdws/p/5645075.html,博客園 蝸牛 2016年6月27日。express

目錄

第01章 異步編程介紹編程

第02章 爲何使用異步編程dom

第03章 手動編寫異步代碼異步

第04章 編寫Async方法async

第05章 Await究竟作了什麼ide

第06章 以Task爲基礎的異步模式異步編程

第07章 異步代碼的一些工具工具

第08章 哪一個線程在運行你的代碼

第09章 異步編程中的異常

第10章 並行使用異步編程

第11章 單元測試你的異步代碼

第12章 ASP.NET應用中的異步編程

第13章 WinRT應用中的異步編程

第14章 編譯器在底層爲你的異步作了什麼

第15章 異步代碼的性能

編寫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);
}

  比較一下這種方式和以前章節所介紹的版本。這看起來更像同步代碼的樣子。沒有任何額外的方法,只在相同結構下有一點額外的代碼。然而,他的行爲和咱們在上一章中的其中一小節(點擊跳轉)所寫的版本很相像。

 
Task和Await

  讓咱們來分解一下咱們寫的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方法返回類型

  標記爲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,可是用不用仍是方法本身的事兒。接口不須要特別聲明出是否要異步。

 
Async方法的return Statement

   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方法如此容易的書寫,這徹底沒有問題。

 
異步匿名委託和Lambdas

   普通的命名方法能夠異步,而且有兩種匿名方法同樣能夠異步。語法和正常的方法也很像。下面是如何使用異步匿名委託的示例:

Func<Task<int>> getNumberAsync = async delegate { return 3; };

   下面是async lambda:

Func<Task<string>> getWordAsync = async () => "hello";

   和普通的異步代碼規則沒什麼不同。你能夠用他們來保持代碼清晰整潔,捕捉閉合,和非異步方法以徹底相同的形式書寫。

寫在最後

  最近好迷茫,可能有點太急躁,總以爲高不成低不就,拼命地想越走越高,又看不到本身明顯的進步。痛苦。

下一章節將介紹   await究竟作了什麼。

相關文章
相關標籤/搜索