简单说说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)指明不捕獲上下文,或所有的方法全部異步到底。
小明聽完,拜別了小花,回到了自己的隔間。
總結
以上是生活随笔為你收集整理的简单说说async/await的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 研发协同平台数据库死锁处理及改进
- 下一篇: Newtonsoft 六个超简单又实用的