淺談async/await

小明用async/await寫了幾年的異步方法,但總沒有完全理解裏面的機制,他決定去請教鄰居小花。

小花聽了小明的描述后說:首先你要明白異步的根本是什麼?大白話解釋異步就是:拉一個人(線程)幫着做一些耗時的事(下載、讀寫數據庫等),我先做別的事了(退出線程),等做好了和我說下,我再繼續做後面的事(恢復上下文)。

小花看到小時還沒有明白,就說:我舉個簡單例子幫你理解吧,假如有兩個方法A和B,A調用B方法,B方法是一個異步方法,這時A不等待B執行完,如圖:

現在兩個方法被分隔幾個小塊,await關鍵字其實就用來隔開同步和異步,上面的方法執行流程如下:

A調用B方法后,B方法在未執行到await之前還是同步方法,比如輸出Sub1還是在當前線程中執行,當方法遇到await后,就會把await后的方法放到新的線程中執行,當前線程則退出函數,由於調用的地方並沒有await,則主線程會繼續執行並輸出Part2,然後結束。等新線程中Thread.Sleep(5000)執行完后,會執行到Console.Write(“Sub2”);這一行代碼會回到原來的線程執行,其實遇到await時會捕獲當前線程的執行上下文,然後給到新線程,新線程在執行完耗時操作后,會判斷之前捕獲到的執行上下方是否為null,如果不為null,則會在上下文中恢復並執行後面的方法,其實就是通過Tak的ContineWith方法註冊回調,如圖:

小明好像聽懂了一些說:現在A方法調用DoSomethingAsync()並沒有等待,如果A方法需要這個方法執行完才能繼續執行,是不是要在DoSomethingAsync()前面加上await?小花回答是,並說:方法只要遇到await,就會把後面的方法給新線程執行,然後線程退出去執行別的方法,等新線程執行完后再通知當前線程恢復上下文繼續執行,如圖:

小明又問:你說異步方法執行完后,後面的方法會在原來的線程中恢復並執行,如果我還想在新線程中繼續執行剩下的代碼,要怎麼辦呢?小花說問的好,await調用新線程執行耗時操作時默認會捕獲當前上下文,如果不想捕獲,則可以調用ConfigAwait(false)方法,如圖:

執行流程如下:

小花補充到,上線提到的線程1、線程2、線程3等不一定準確,因為異步的回調是使用線程池中的線程,所以回調有可能還在原來線程中執行,這個主要看操作系統的調度。

小明滿意的點點頭又問:我經常聽同事說用異步方法會死鎖,這又是為什麼呢?小花聽了說,他們肯定是在調用異步方法的時候使用.Result(),如圖:

小花指着圖解釋說:上面的代碼task.Result()會阻塞線程等待task返回結果,DoSomethingAsync方法在執行完Thread.Sleep(5000)后,發現捕獲到的上下文不為空,則會嘗試將Console.Write(“Sub2”)這行代碼交由調用線程去執行,而這時調用線程還在等待,就這樣互相卡着對方,就造成了死鎖,如圖:

小明點了點頭又問:那要怎麼避免呢?小花說出現這種情況也和框架有關,像WinForm為了讓所有UI操作都在主線程中執行,就添加了一個SynchronizationContext類實例用以表示當前上下文,而像控制台等項目這個SynchronizationContext實例默認為null,所以即使使用.Result也不會死鎖。但最好使用異步的時候不要用.Result,可以使用ConfigAwait(false)指明不捕獲上下文,或所有的方法全部異步到底。

 

小明聽完滿意地回到自己的隔間。

 

更多精彩,請關注我的公眾號:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

您可能也會喜歡…