概念
什麼是異步編程?
異步編程是可以讓程序並行運行的一種手段,其可以讓程序中的一個工作單元與主應用程序線程分開獨立運行,並且在工作單元運行結束後,會通知主應用程序線程它的運行結果或者失敗原因。使用異步編程可以提高應用程序的性能和響應能力。[^1]
應當注意的是,所謂的異步編程能提高效率這句話並不嚴謹,嚴格的來說它是利用了等待時間以優化整體的時間效率,而對於其中任意一項工作其本來的效率並沒提高。
如果你對此概念的理解還是十分抽象,下面我們用一道小學數學題來舉例。
小明的媽媽做飯要a分鐘,燒水要b分鐘,請問小明媽媽燒水並做飯一共要多長時間(a與b均大於0)?我們不妨記最終所用時間為T,則有如下情況:
-
對於傳統的同步編程來講,小明媽媽要先燒水然後做飯或者先做飯然後去燒水,燒水或做飯的時間內是無法做其他事情的,這個等待的過程我們稱為阻塞的。這樣所用的總時間如下
然而對於異步編程,就是讓小明媽媽將燒水壺通電進行燒水,燒水由燒水壺負責,而小明媽媽可以一邊做飯一邊等水燒開。簡單來講就是在等待一件事情完成的同時,利用這段空閒時間去做其他事情,這個過程是非阻塞的。所以所用時間如下:
然而由簡單的數學知識可知
x≤max(a,b)<a+b, x∈{a,b}x≤max(a,b)<a+b, x∈{a,b}所以問題來了:對於燒水或做飯的效率提高了嗎?答案是沒有。因為燒水仍然要b分鐘,做飯仍然要a分鐘。然而對於整體的效率卻得到了提升,就如上方公式所表示的那樣。
async\await關鍵字
異步方法的定義
在.net中所謂的異步方法,一般是指async關鍵字修飾的方法。該方法有如下特點:
-
異步方法的返回值一般是
Task<T>
,T是真正的返回值類型,如Task<int>
。即使方法沒有返回值,也最好把返回值聲明為非泛型的Task。(按鈕等控件事件響應方法用void) -
異步方法名字以Async結尾。
-
調用異步方法時,一般方法前面加上await關鍵字,這樣返回值就是泛型指定的T類型。
一個方法中如果有await調用的異步方法,那麼該方法也必須是async修飾的異步方法。
下面我們利用C#自帶的異步同步方法寫入再讀取txt文件
同步方法:
儘管這種方式可以達成目的,但是還是不推薦,因為這種方式可能會面臨死鎖的風險。
死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。[^2]
異步方法的線程委託
在一些情況下我們可能會將異步方法放到線程池來執行。
如果該方法是用正則表達式寫的匿名方法的話,則只需要在前面使用async關鍵字即可。
通過查看代碼我們便知道,真正的Main實際上是void類型的,這是編譯器幫我們搞定的,這個Main中調用了寫代碼的時候被async修飾返回值為Task的Main函數。
而通過查看<Main>d_0
的代碼我們可以分析出async與await的底層原理:
-
async的方法會被C#編譯器編譯成一個類,會主要根據await調用切分成多個狀態,對async方法的調用會被拆分為MoveNext的調用。
-
await看似是等待,實際上編譯後沒有等待。await調用的等待期間,.net會把當前的線程返回給線程池,等異步方法調用執行完畢後,框架會從線程池再取出來一個線程執行後續代碼。此外這裡還進行了優化,到要等待的時候如果發現已經執行結束了,那就沒必要切換線程了,剩下的代碼繼續在之前的線程上執行。
結束
異步方法的基本內容大概就這些了