文法上有如下特征:
方法使用async作為修飾符
方法内部包含一個或者多個await表達式
方法傳回類型必須是 void 、task 、task<t>三者中之一
異步方法的參數可以任意類型,但是不能為out和ref參數
約定俗成,一般異步方法都是以 async作為字尾的。
除了方法之外,lambda表達式和匿名函數也可以作為異步對象。
傳回類型詳解:
task類型:如果調用方法不需要從異步方法中傳回某個值,但需要檢查異步方法的狀态,可以傳回一個task,此時就算異步方法中出現了return語句,也不會傳回任何東西(其實都不需要顯示使用return語句)。
task<t>類型,除了上面task的功能,還可以通過 return屬性來傳回t類型的值。
void類型:如果僅僅是執行異步方法,而不需要與它做任何進一步的互動(“調用并忘記”),此時可以用void,和task一樣,就算有return語句,也得不到任何東西(其實都不需要顯示使用return語句)。注意,傳回值為void的異步函數前面不能使用await。而調用其他兩種傳回值的異步方法時,如果沒有用await,編譯器會給出警告。
傳回類型使用原則:
對于需要傳回對象的方法,使用task<t>
當一個方法屬于觸發後不用理會什麼時候完成的方法,可以使用void,例如事件處理函數(event handler)
當雖然不需要傳回結果,但卻需要知道是否執行完成的方法時,傳回一個task。例如異步方法涉及資料更新邏輯,而調用該異步方法的方法在需要在調用異步方法後讀取最新資料,這種情況需要用task傳回類型,而不能用void,否則可能讀取不到最新資料。
異步方法的建立和使用三個部分組成:
調用方法(calling method):該方法調用異步方法,在異步方法執行其任務的時候繼續執行
異步方法(async)
await表達式:用于異步方法内部,指明需要異步執行的内容。一個異步方法可以包含任意多個await表達式,如果一個都不包含編譯器會發出警告
異步方法由三部分組成:
await之前的部分
await表達式
後續部分:await之後的部分(如果有多個await,那就是await之間和最後一個await之後的部分)
這裡有幾個需要注意的問題:
await之前的部分是同步執行的(确切說是第一個await之前)
當達到awati的時候,會将異步方法的控制傳回給調用方法。如果方法傳回的類型是task或者task<t>,将建立一個task對象(表示需異步完成的任務和後續)傳回到調用方法。 這裡的傳回值并不是await表達式的傳回值,而是異步方法中聲明的傳回值類型(即task或者task<t>)
異步方法内部需要完成以下工作:
異步執行await表達是的空閑任務
當await表達式執行完成之後,執行後續部分。後續本身也可能是await表達式,處理過程和上一個一緻。
後續部分如果遇到 return或者方法達到末尾,将做如下的事情:(這個點要注意下,并不是遇到return或者達到方法末尾,就能擷取到傳回值,它隻是退出了(異步任務可能還沒執行完畢呢))
如果傳回的類型是void,控制流就退出了
如果傳回的類型是task,後續部分設定task對象的屬性并退出
如果傳回的類型是task<t>,不僅要設定task對象屬性,還要設定task對象的result屬性
調用方法繼續執行,根據第二步擷取task對象(void除外),當需要其實際值的時候,就引用task對象中的result屬性。屆時,如果異步方法設定了該屬性,調用方法擷取其值并繼續。否則就等待該屬性被設定,然後再繼續執行。

該示範是在控制台應用程式中完成的,我們可以看到,主線程的id為10,第一個await和緊接着之後的代碼的線程id為6和11,第二個await和緊接着之後的代碼的線程id為6,在非ui的線程中執行async異步方法,await等待的異步操作和之後接着要執行的代碼,都是從線程池中擷取了一個線程來執行代碼,并且從線程池中擷取的也不一定是同一個線程。
這個示範可以看到,在ui線程中使用async異步方法的時候,await後緊接着的代碼,一直都會是在ui線程中執行。是以,在使用的時候需要注意這一點,在ui與非ui線程中執行async異步方法的時候,需要注意這兩個細節,否則将會帶來不必要的麻煩,例如:
如代碼所示,定義一個task類型傳回值的異步方法,先執行這個異步方法methodasync1(),然後擷取該異步方法的傳回值,在這個擷取傳回值的過程中,執行異步方法的原來的線程也會一直阻塞直到等到擷取到傳回值,毫無意外的程式在運作一秒鐘以後,輸出1。但是在ui線程時,如下所示:
同樣的是定義一個帶task類型傳回值的異步方法,同樣的也是先執行這個異步方法methodasync1(),然後擷取該異步方法的傳回值,但是,在這裡,我們使用了winform程式來執行,該異步方法是在ui線程調用的,那麼,現在就出現問題了,一旦點選button1按鈕,程式便卡死了。
上面說過了,在ui線程中調用的異步方法,在其await操作之後的一些代碼,仍然實在ui線程中執行的,那麼,首先,點選button1按鈕按鈕,異步方法執行await中的異步任務,調用函數繼續執行到 richtextbox1.text = a.result.tostring()這句被阻塞ui線程等待異步結果,而await任務執行完以後,return 1這個操作也需要在ui線程中執行,但ui已經被阻塞了,無法執行任何代碼,就這樣,我在等你傳回值,你在等我釋放ui線程來給你執行代碼,誰也不相讓,造成了程式的卡死。
其實上面的寫法有問題,下面正确的寫法可以避免卡死:
async task或 aync task<t> 意味着該方法實際上會自動傳回對正在進行的操作的引用,然後您可以在其他地方等待它。在這個傳回的引用中,包括異常對象,以及我們等的結果和狀态。而async void,沒有task對象,并不能傳回這些内容。是以兩着的異常處理有差別。
1.async task或 aync task<t>的異常流
以上代碼的運作結果如下:
異常從 async task throwexception() 再次抛出後,在調用函數awaitexception_event()處再被截獲,這是我們期望的結果。
2.task.wait()的異常流
将代碼改為如下,調用throwexception這個task時,不使用await,而使用wait(),如下
那麼便得到這樣的結果:
這是因為異常在task内部都儲存在aggregateexception對象裡。每一個task都會存一個異常清單。當你await 一個task時,第一個異常會重新抛出來,是以你可以捕獲指定的異常類型(比如invalidoperationexception)。但是,當你使用task.wait或者task.result同步地阻塞task時,所有的異常都被封裝在aggregateexception抛出來。可見 await task時,異常處理會更容易。
如果在20行不用await,編譯程式,有以下兩個提醒:
warning cs4014: because this call is not awaited, execution of the current method continues before the call is completed. consider applying the 'await' operator to the result of the call.
warning cs1998: this async method lacks 'await' operators and will run synchronously. consider using the 'await' operator to await non-blocking api calls, or 'await task.run(...)' to do cpu-bound work on a background thread.
運作時發現結果不一樣了,發現收不到異常了。異常當然不會憑空消失,但是如果不用await,這個異常就像被“吞噬”一樣。這是因為,調用程式沒有await,沒有得到這個task,同樣無法得到這個task中的異常,異常無法回到調用代碼的上下文環境。
可見,調用async task一定要使用 await。
3.async void的異常流
将throwexception的傳回類型改為async void。那麼此時編譯器也會告訴不能在調用throwexception的地方使用await。
運作後,我們得到這個結果,異常沒有被“吞噬”,而是直接抛出來了。在awaitexception_event中的try…catch并不能catch到。這是因為這個方法沒有task,在awaitexception_event中我們也并不能await,是以異常就在程式目前上下文直接抛出來。因為這是控制台程式,如果在gui(如wpf和uwp)程式中,在全局的unhandled_exception中,我們可以收到這個異常。但是如果有多個這樣的async void抛出類似這種異常,我們并不能區分以有效處理。可見使用async void是非常危險的行為, 代碼中也避免使用async void (除了異步事件或委托)。
以上三種情況可得,在async,await中,最佳的異常處理就是異步函數使用async task或者async task<t> ,使得調用者可以通過await得到函數的引用,獲得期間的結果和異常資訊。