天天看點

C#5.0-原生異步程式設計方式

帶着問題去思考!大家好

 簡介

微軟提供的最新的異步程式設計基礎設施。它允許我們以子產品化的方式設計程式,來組合不同的異步操作。

1:遺憾的是,當閱讀此類程式時仍然非常難了解程式實際執行順序。很多大型的程式中将會有許多互相依賴的任務和後續操作,處理異常的後續操作,并且它們都會出現在程式代碼中的不同地方,是以了解程式的先後執行次序很難。

2:能夠接觸使用者界面控制的每個異步任務是否得到了正确的同步上下文。程式隻允許通過UI線程使用這些控制器,否則将會得到多線程的通路異常。說到異常,我們不得不使用單獨的後續操作任務來處理在之前的異步操作中發生的錯誤。這又導緻分散在代碼的不同部分的複雜的處理錯誤的代碼。邏輯無法互相關聯。

C#5.0引入了新的語言特性。異步函數,它是TPL之上的更進階别的抽象。真正簡化了異步程式設計。

須知:

  • 建立一個異步函數,首先需要用async關鍵字标注一個方法。
  • 異步函數必須傳回Task或Task<T>類型,可以使用async void 方法,但是更推薦使用async task方法,
  • 使用async标注的方法内部,可以使用await操作符,該操作符可與TPL的任務一起工作,并擷取該任務中異步操作的結果。
  • 異步函數在其代碼中至少擁有一個await操作符。(沒有則隻是警告)
  • 不能在catch,finally,lock或unsafe代碼塊中使用await操作符。
  • 不允許對任何異步函數使用ref或out參數。

如果程式中有兩個連續await操作符,他們是順序運作的,第一個完成後第二個才會開始運作

使用await操作符擷取異步任務結果

View Code

C#5.0-原生異步程式設計方式

這裡需要注意的是Task.Wait和Task.Result方法,如果不是百分百知道代碼是幹什麼的,很容易造成死鎖。

對連續的異步任務使用await操作符

同樣運作兩個異步操作,首先AsynchronyWithAwait方法将講,這裡使用了兩個await聲明,最重要一點是該代碼是順序執行,閱讀代碼很清晰,但是該程式如何是異步程式呢?首先,他不總是異步的,當使用await時如果一個任務已經完成,我們會異步地得到該任務結果。否則,當代碼中看到await聲明時,通常的行為是方法執行到該await代碼行時将立即傳回,并且剩下的的代碼将會在一個後續操作任務中運作,是以等待操作結果時并沒有阻塞程式執行。這是一個異步調用。

異步并不總是意味着并行執行。

 對并行執行的異步任務使用await操作符

這裡定義了兩個異步任務,分别執行了3秒和5秒。然後使用了Task.WhenAll輔助方法建立另一個任務,該任務隻有在所有底層任務完成後才運作,之後我們等待該組合任務的結果。這裡兩個任務似乎是被線程池中的同一個線程執行的。

兩個任務被不同的工作線程執行,不同之處在于Task.Delay在幕後使用了一個計時器,從線程池中擷取工作線程,它将等待Task.Delay方法傳回結果。然後,Task.Delay方法啟動計時器并指向一塊代碼,該代碼會在計時器時間到了Task.Delay方法中指定的秒數後被調用。之後即将工作線程傳回到線程池中。當計時器事件運作時,我們又從線程池中任意擷取一個可用的工作線程(可能就是運作一個任務時使用的線程

當使用Task.Run方法時,從線程池中擷取一個工作線程并将其阻塞幾秒,具體秒數由Thread.Sleep方法提供。然後擷取了第二個。 工作線程并且将其阻塞。在這種場景下,我們消費了兩個工作線程,而它們絕對什麼事沒做,因為在它們等待時不能執行任何其他操作。

處理異步操作中的異常

這裡實作了三個場景來展示在C#中使用async和await時關于錯誤處理的最常見情況,一種情況最簡單,與同步代碼幾乎一樣,使用try/catch聲明即可擷取異常細節。一種很常見的錯誤是對一個以上的異步操作使用await時還使用以上方式。如果跟第一種處理的話,則隻能從底層的AggregateException對象中得到第一個異常。為了收集所有異常資訊,可以使用await任務的Exception屬性,在第三種情況中,我們使用AggregateException的Flatten方法将層級異常放入一個清單,并且從中提取出所有的底層異常。