前言
C# 中的 Async 和 Await 關鍵字是異步編程的核心。使用這兩個關鍵字可以輕鬆創建異步方法。使用 async
關鍵字定義的異步方法簡稱“異步方法”。
異步編程
併發的一種形式,它採用furture模式或回調(callback)機制,以避免產生不必要的線程。.Net中future的類型有 Task
和 Task<Result>
。
異步編程的核心理念是異步操作:
啓動了的操作將會在一段時間後完成。
這個操作正在執行時,但不會阻塞原來的線程。
啓動了這個操作的線程後,可以繼續執行其它任務。
當操作完成時,它會通知它的future,或者調用回調函數,以便讓程序知道操作已經結束。
異步的好處
-
對於面向終端用戶的GUI程序,異步可以提高響應能力。
-
對於服務器應用:異步編程實現了可擴展。
服務器可以利用線程池填滿其可擴展性,使用異步編程後,可擴展性通常可以提供一個數量級,可以最大程度的壓榨服務器性能,提高處理能力。
async
使用 async
修飾符可以將方法、lambda表達式或匿名方法指定爲異步。
async
的主要目的是,使方法內的await關鍵字生效。
//等待異步完成再執行後邊的操作,但是整個方法不會阻塞
var result = await DoSomethingAsync();
output.Result = result;
如果使用了 Async
最好一直使用它
await
async
標記的異步方法,可以使用 await
來指定暫定點。 await
運算符通知編譯器異步方法:在等待的異步過程完成後才能繼續通過該點。同時,會將控制權返回至異步方法的調用方。
async
方法在開始時以同步的方式執行。在 async
方法內部,await
關鍵字對他的參數(一個異步任務)執行一個異步等待。它首先檢查操作是否已經完成,如果完成了,就繼續運行(同步方法)。否則,他會暫停 async
方法,並返回,將控制權交給調用方,留下一個 未完成的 Task
。一段時間後,操作完成,async
方法再恢復運行。
用 await
語句等待一個任務完成,當該方法在 await
處暫停時,就可以捕捉上下文(context)。如果當前SynchronizationContext
不爲空,這個上下文就是當前SynchronizationContext
。如果當前SynchronizationContext
爲空,則這個上下文爲當前TaskScheduler
。該方法會在這個上下文中繼續運行。
//此時await會捕獲當前上下文
await DoSomethingAsync();
//.... //這裏會試圖用上邊捕獲的上下文繼續執行
await DoSomethingAsync().ConfigureAwait(false);
//.... 這裏開始在新的線程中運行
ConfigureAwait
配置 Task
的 awaiter
,將延續任務封裝回原始上下文,則爲True
,否則爲 False
。
詳情可查閱ConfigureAwait(false)資料,這裏暫時不做贅述。可閱讀以下文章
異步方法異常:
異步方法異常時會返回在 Task 對象中,並將這個 Task 對象的狀態改變爲“已完成”。當 await 調用該 Task 對象時,await 會獲得並(重新)拋出該異常,並保留原始的棧軌跡。
注意:
異步方法避免使用 Task.Wait
和 Task<T>.Result
,因爲他們會導致死鎖。
示例:
public async Task<int> GetUrlContentLengthAsync()
{
var client = new HttpClient();
//異步執行請求,立即返回一個Task<string>,並將控制權讓出
Task<string> getStringTask =
client.GetStringAsync("https://docs.microsoft.com/dotnet");
//由於異步方法未執行等待,所以可以繼續執行不依賴異步返回結果的同步方法
DoIndependentWork();
//掛起任務進度,並將控制權交割GetUrlContentLengthAsync方法的調用方,並返回一個Task<int>給調用方。
//該任務表示將返回下載字符串長度的一個承諾
//然後調用方會繼續執行,執行不依賴於GetUrlContentLengthAsync返回結果的其它工作,否則就等待。
string contents = await getStringTask;
return contents.Length;
}
void DoIndependentWork()
{
Console.WriteLine("Working...");
}
await
運算符會暫停 GetUrlContentLengthAsync
方法:
- 在
getStringTask
完成之前,GetUrlContentLengthAsync
無法繼續。 - 同時,控件返回至
GetUrlContentLengthAsync
的調用方。 - 當
getStringTask
完成時,控件將在此繼續。 - 然後,
await
會從getStringTask
檢索string
結果
如果 DoIndependentWork
依賴於異步執行的結果,則在等待 getStringTask
返回結果期間不能進行任何工作。需要改成以下寫法。
string getStringTask = await client.GetStringAsync("https://docs.microsoft.com/dotnet");
構成異步方法的條件:
- 方法簽名要包含
async
修飾符。 - 按照約定,異步方法的名稱以“Async”後綴結尾。
- 返回類型爲以下類型之一
- 如果你的方法有返回值,則返回 Task<Result> 的類型。
- 如果你的方法沒有返回值,則返回 Task 類型
- 方法中至少要包含一個 await 表達式,該表達式標記一個點,在該點上,直到等待的異步操作完成方法才能繼續。 同時,並且將控制權返回到方法的調用方。
返回類型
await 運算符的操作數通常是以下幾種.NET類型:Task、Task<TResult>、ValueTask或VauleTask<TResult>。但是任何可等待表達式都可以是await運算符的操作數。
總結
- 異步可以提高響應能力。
- 異步不會阻塞線程
- 使用
async
來標記異步方法 - 使用
await
來指定暫停點,掛起其進度,在等待的異步過程完成後才能繼續通過該點。同時,會將控制權返回至異步方法的調用方,調用方可以繼續執行不依賴於異步返回結果的其它工作。 - 如果使用了
Async
最好一直使用它 - 異步方法避免使用
Task.Wait
和Task<T>.Result
,因爲他們會導致死鎖。