.NET 中 async 和 await

前言

C# 中的 Async 和 Await 關鍵字是異步編程的核心。使用這兩個關鍵字可以輕鬆創建異步方法。使用 async 關鍵字定義的異步方法簡稱“異步方法”。

異步編程

併發的一種形式,它採用furture模式或回調(callback)機制,以避免產生不必要的線程。.Net中future的類型有 TaskTask<Result>

異步編程的核心理念是異步操作:

啓動了的操作將會在一段時間後完成。

這個操作正在執行時,但不會阻塞原來的線程。

啓動了這個操作的線程後,可以繼續執行其它任務。

當操作完成時,它會通知它的future,或者調用回調函數,以便讓程序知道操作已經結束。

異步的好處

  1. 對於面向終端用戶的GUI程序,異步可以提高響應能力。

  2. 對於服務器應用:異步編程實現了可擴展。

    服務器可以利用線程池填滿其可擴展性,使用異步編程後,可擴展性通常可以提供一個數量級,可以最大程度的壓榨服務器性能,提高處理能力。

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 配置 Taskawaiter,將延續任務封裝回原始上下文,則爲True ,否則爲 False

詳情可查閱ConfigureAwait(false)資料,這裏暫時不做贅述。可閱讀以下文章

異步方法異常:

異步方法異常時會返回在 Task 對象中,並將這個 Task 對象的狀態改變爲“已完成”。當 await 調用該 Task 對象時,await 會獲得並(重新)拋出該異常,並保留原始的棧軌跡。

注意:

異步方法避免使用 Task.WaitTask<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");

構成異步方法的條件:

  1. 方法簽名要包含 async 修飾符。
  2. 按照約定,異步方法的名稱以“Async”後綴結尾。
  3. 返回類型爲以下類型之一
    1. 如果你的方法有返回值,則返回 Task<Result> 的類型。
    2. 如果你的方法沒有返回值,則返回 Task 類型
  4. 方法中至少要包含一個 await 表達式,該表達式標記一個點,在該點上,直到等待的異步操作完成方法才能繼續。 同時,並且將控制權返回到方法的調用方。

返回類型

await 運算符的操作數通常是以下幾種.NET類型:Task、Task<TResult>、ValueTask或VauleTask<TResult>。但是任何可等待表達式都可以是await運算符的操作數。

總結

  1. 異步可以提高響應能力。
  2. 異步不會阻塞線程
  3. 使用 async 來標記異步方法
  4. 使用 await 來指定暫停點,掛起其進度,在等待的異步過程完成後才能繼續通過該點。同時,會將控制權返回至異步方法的調用方,調用方可以繼續執行不依賴於異步返回結果的其它工作。
  5. 如果使用了 Async 最好一直使用它
  6. 異步方法避免使用 Task.WaitTask<T>.Result ,因爲他們會導致死鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章