多線程之旅(4)_async/await的用法精細詳解

原文鏈接:https://blog.csdn.net/yangwohenmai1/article/details/89893746

GitHub源碼地址:稍後

async/await是個常見但不常用的方法。常見是因爲在比較官方的代碼。片段裏經常見到這樣的搭配,不常用是因爲作爲開發人員來說,我們常常有更熟知的方法去代替他。

async/await到底有什麼用呢,其實網上也很少有說的明白的文章,下面我來儘量簡單明瞭的解釋一下。

1.使用場景
async/await這兩個關鍵字用在線程同步/異步的場景中。

2.語法方法
async和await是一種搭配用法,可以理解爲兩者一般會同時出現。在寫一個方法時,async寫在定義方法的地方,await則寫在定義的方法內部。如下所示:

async Task<int> f() {
    var value = await Task.Run(()=>gosleep())
    return value.Reslut;
}
 
public static int gosleep()
{
    Thread.Sleep(1000);
        return 1;
}
當然你也可以只寫async而不寫await,程序會給你一些警告,建議你不要這樣做,因爲這種做法沒有任何意義,這種做法會導致你不得不去阻塞主進程。具體原因後面會講

3.功能作用
async/await這兩個關鍵詞第一個很重要的用處,是說明作用,或者說可以增加代碼的可讀性。第二個作用就是表明哪些代碼要進行同步處理

當定義函數出現async時,說明這個函數中有異步的功能。這個函數要異步執行。

當函數內部出現await關鍵字時,說明在await後面跟的方法就是一個異步的方法。

4.使用async/await的特性和優點
await這個關鍵字後面表示啓動了一個線程,那問題來了,我們用Thread不行嗎,用普通的Task不行嗎?

首先,《C#併發編程經典實例》這本書上說過,編寫多線程的時候,當你開始寫new Thread時,你就已經輸了。Task是現在更好的線程處理方式,而Thread已經開始過時了。

其次,Thread一旦釋放出去,基本就無法掌握其運行狀態。比如:這個Thread線程所處理的工作結束了沒有?它應該生成的數據生成完沒有?我主進程是不是已經可以使用這個線程生成的數據了?如果線程還沒有結束,主進程還要等待多久?

當然我們可以通過一些投機取巧的方法解決這個問題,比如設置一些標記,當線程運行完成時,修改這個標記的值,主進程如果想要使用線程所生成的數據,就去循環判斷這個標記值是否被標記爲完成(雖然看起來比較傻,但事實上這有可能是最安全的方法)。

如果你不想用這種很傻的,很容易被同事和領導吐槽的方法,你就可以考慮在調用的方法中使用await這個關鍵字。

(1)使用包含await標記的線程,一般是可以擁有返回值的,也就是線程可以用return來返回自己所生成的數據。

(2)當主進程需要用到這個線程返回的數據時,如果線程已經執行完成,主進程可以直接獲取線程生成的數據;

(3)如果這個線程還沒有執行完,當主進程試圖獲取線程的返回值的時候,就會開始等待,不再繼續執行。也就是主進程會阻塞等待,從而避免拿到錯誤的數據。

(4)直到線程處理完成後,主進程成功讀取到線程return的數據,然後繼續執行後續的代碼。

最後,總的來說,如果你的程序:

(1)又想啓用多線程模式,

(2)主進程裏後續又有代碼必須依賴前面異步線程的計算結果,

(3)又不能很好的控制這兩者之間的時長間隔,

(4)又想盡量減少主進程的阻塞,

那麼就來用async/await吧。而且一旦你開始使用await後,你就會發現Task原來有一堆方便的線程同步方法可以一起使用,比如WaitAll,WhenAll,ContinueWith,Delay,GetAwaiter等等一套線程同步全家桶,用起來非常方便。

5.async/await的使用注意事項
最後在描述一遍async/await的用法,當一個被調用的方法中有線程時,這個方法要用關鍵字async修飾,這個方法中關於線程調用的代碼前,要用await關鍵字修飾。代碼見第二部分給出的示例。

當主進程走到代碼內部時,如果發現有await關鍵字,就知道此處有線程要異步執行,主進程就會自動執行後續的其他代碼,直到遇到需要使用此線程返回值得地方,主進程纔會考慮是否要停下來等待。這樣就解決了主進程阻塞的問題。

需要注意的地方是什麼呢?

async/await一定要一起使用,否則會失去意義。只使用async一個關鍵字時,再返回結果的時候就會報錯,不能返回一個Task<int>類型結果如下圖:

程序會說報錯:

因此你只能強行改變返回值類型,最後修改成如下形式:

ok,你在返回數據的時候,改成了return result.Result這種返回值,此時程序不再報錯。但事實上此時的程序已經不再是異步的了。

原因很簡單:return result.Result中的Result,其實是var result = Task.Run(() => gosleep20())這行代碼裏返回的數據結果;如果要獲取result.Result,也就說明程序要在return result.Result這行代碼上阻塞主進程,換句話說是主進程會被困在這個假異步方法中出不去;直到var result = Task.Run(() => gosleep20())這行代碼執行完成,才能獲取到result.Result的數據,同時放開阻塞,主進程才能繼續運行。

因此這個異步也就變得沒有意義了。

6.async/await的數據流轉圖
下面這個圖

關係圖中的數值對應於以下步驟。

1.事件處理程序調用並等待 AccessTheWebAsync 異步方法。

2.AccessTheWebAsync 可創建 HttpClient 實例並調用 GetStringAsync 異步方法以下載網站內容作爲字符串。

3.GetStringAsync 中發生了某種情況,該情況掛起了它的進程。 可能必須等待網站下載或一些其他阻止活動。 爲避免阻止資源,GetStringAsync 會將控制權出讓給其調用方 AccessTheWebAsync。

GetStringAsync 返回 Task,其中 TResult 爲字符串,並且 AccessTheWebAsync 將任務分配給 getStringTask 變量。 該任務表示調用 GetStringAsync 的正在進行的進程,其中承諾當工作完成時產生實際字符串值。

4.由於尚未等待 getStringTask,因此,AccessTheWebAsync 可以繼續執行不依賴於 GetStringAsync 得出的最終結果的其他工作。 該任務由對同步方法 DoIndependentWork 的調用表示。

5.DoIndependentWork 是完成其工作並返回其調用方的同步方法。

6.AccessTheWebAsync 已用完工作,可以不受 getStringTask 的結果影響。 接下來,AccessTheWebAsync 需要計算並返回該下載字符串的長度,但該方法僅在具有字符串時才能計算該值。

因此,AccessTheWebAsync 使用一個 await 運算符來掛起其進度,並把控制權交給調用 AccessTheWebAsync 的方法。 AccessTheWebAsync 將 Task(Of Integer) 或 Task<int> 返回至調用方。 該任務表示對產生下載字符串長度的整數結果的一個承諾。

 備註

如果 GetStringAsync(因此 getStringTask)在 AccessTheWebAsync 等待前完成,則控件會保留在 AccessTheWebAsync 中。如果異步調用過程 (getStringTask) 已完成,並且 AccessTheWebSync 不必等待最終結果,則掛起然後返回到 AccessTheWebAsync 將造成成本浪費。

在調用方內部(此示例中的事件處理程序),處理模式將繼續。 在等待結果前,調用方可以開展不依賴於 AccessTheWebAsync 結果的其他工作,否則就需等待片刻。事件處理程序等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync。

7.GetStringAsync 完成並生成一個字符串結果。 字符串結果不是通過按你預期的方式調用 GetStringAsync 所返回的。(請記住,此方法已在步驟 3 中返回一個任務。)相反,字符串結果存儲在表示完成方法 getStringTask 的任務中。 await 運算符從 getStringTask 中檢索結果。 賦值語句將檢索到的結果賦給 urlContents。

8.當 AccessTheWebAsync 具有字符串結果時,該方法可以計算字符串長度。 然後,AccessTheWebAsync 工作也將完成,並且等待事件處理程序可繼續使用。 在此主題結尾處的完整示例中,可確認事件處理程序檢索並打印長度結果的值。

如果你不熟悉異步編程,請花 1 分鐘時間考慮同步行爲和異步行爲之間的差異。 當其工作完成時(第 5 步)會返回一個同步方法,但當其工作掛起時(第 3 步和第 6 步),異步方法會返回一個任務值。 在異步方法最終完成其工作時,任務會標記爲已完成,而結果(如果有)將存儲在任務中。

7.總結

好了,搞了這麼老半天終於說完了

附上兩個很厲害的鏈接:

https://docs.microsoft.com/zh-cn/previous-versions/hh191443(v=vs.120)

https://docs.microsoft.com/zh-cn/previous-versions/hh873191%28v%3dvs.120%29

測試代碼見文首GitHub鏈接

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