對 ASP.NET 非同步程式設計的一點理解
本來這篇博文想探討下非同步中的異常操作,但自己在做非同步測試的時候,又對 ASP.NET 非同步有了新的認識,可以說自己之前對非同步的理解還是有些問題,先列一下這篇博文的三個解惑點:
- async await 到底是什麼鬼???
- 非同步操作中發生異常,該如何處理?
- 非同步操作中發生異常(有無 catch throw 情況),Application_Error 會不會捕獲?
之前測試過非同步中的同步(很多種情況),這次我們把測試程式碼寫更復雜些(非同步中再進行非同步),程式碼如下:
[Route("")] [HttpGet] public async Task<string> Index() { System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId); var result = await Test(); System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId6:" + Thread.CurrentThread.ManagedThreadId); return result; } public static async Task<string> Test() { System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId); using (var client = new HttpClient()) { var response = await client.GetAsync("http://stackoverflow.com/questions/14996529/why-is-my-async-asp-net-web-api-controller-blocking-the-main-thread"); await Test2(); System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId5:" + Thread.CurrentThread.ManagedThreadId); return await response.Content.ReadAsStringAsync(); } } public static async Task<string> Test2() { System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId); using (var client = new HttpClient()) { var response = await client.GetAsync("http://stackoverflow.com/questions/33408905/pgadminiii-bug-on-query-tool"); System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId); return await response.Content.ReadAsStringAsync(); } }
輸出結果(執行四次):
Thread.CurrentThread.ManagedThreadId1:8 Thread.CurrentThread.ManagedThreadId2:8 Thread.CurrentThread.ManagedThreadId3:6 Thread.CurrentThread.ManagedThreadId4:6 Thread.CurrentThread.ManagedThreadId5:6 Thread.CurrentThread.ManagedThreadId6:6 Thread.CurrentThread.ManagedThreadId1:7 Thread.CurrentThread.ManagedThreadId2:7 Thread.CurrentThread.ManagedThreadId3:8 Thread.CurrentThread.ManagedThreadId4:7 Thread.CurrentThread.ManagedThreadId5:7 Thread.CurrentThread.ManagedThreadId6:7 Thread.CurrentThread.ManagedThreadId1:5 Thread.CurrentThread.ManagedThreadId2:5 Thread.CurrentThread.ManagedThreadId3:5 Thread.CurrentThread.ManagedThreadId4:6 Thread.CurrentThread.ManagedThreadId5:6 Thread.CurrentThread.ManagedThreadId6:6 Thread.CurrentThread.ManagedThreadId1:8 Thread.CurrentThread.ManagedThreadId2:8 Thread.CurrentThread.ManagedThreadId3:8 Thread.CurrentThread.ManagedThreadId4:8 Thread.CurrentThread.ManagedThreadId5:8 Thread.CurrentThread.ManagedThreadId6:8
這個測試方法,我執行了無數次,大致就是上面的四種情況,我當時看到輸出結果,其實是很凌亂的,我也大家也一樣,並心裡有一些疑問:你這真是非同步程式設計嗎?為啥執行緒千奇百怪?並且最後那個還只有一個執行緒,這和同步有啥區別???
針對上面這個疑問,我想了很久,並對自己產生了一些質疑的聲音:你每天都在寫 async await 程式碼,你真的瞭解它嗎???然後我又重新找到上面那篇 jesse liu 的博文,反覆讀了很多篇,最後終於有了一些“頓悟”,結合上面的測試程式碼,我大致畫了一張示意圖:
結合上面的圖,我說一下自己的理解,在做測試的時候,HttpClient.GetAsync 儘量讓它執行時間長些,比如請求的 URL 可以是 stackoverflow 或 github(原因你懂得!),因為有個時間差,這樣我們可以更好的瞭解執行緒的執行情況,上面圖中“執行緒1、執行緒1x、執行緒3x、執行緒4x”等等,這些並不是不同執行緒,也就是說執行緒1有可能等於執行緒1x或執行緒3x。。。從上面的輸出結果就可以看出,用執行緒x來表示兩個輸出之間所經歷的 await 次數,這就證明了一個疑惑:await 並不一定會建立和之前不一樣的執行緒。
到底什麼是非同步???我個人覺得,async 非同步是一個偽概念,await 等待才是精髓,一個執行緒可以響應多個請求,如果是同步程式設計,一個執行緒在處理某一個請求的時候阻塞了(比如上面測試程式碼中的 HttpClient.GetAsync 網路操作),那麼這個執行緒就會一直等待它處理,在這個等待的過程中,那麼其他請求就不能再使用這個執行緒,又因為 IIS 執行緒池中的執行緒數量有限,那麼同步程式設計下,高併發將是一個頭疼的問題,試想一下,如果執行緒池中的執行緒數量為 100 個,這 100 個執行緒在同時處理 100 個請求的時候,都悲催的阻塞掉了,這時候第 101 個請求將無法執行,那麼併發量就是 100。
接上面,同樣的處理過程,如果是非同步程式設計,那將是什麼情況呢?比如一個執行緒在處理某一個請求的時候,執行到 await 操作,那麼這個執行緒將會釋放回到執行緒池,然後進行等待,等待的過程中,原來的那個執行緒就可以處理其他請求或者這個請求的其他操作,注意等待並不是執行緒等待,而是操作等待,我原來就很不理解這個地方,如果是執行緒等待,就表示這個執行緒會一直等待它完成,那和同步程式設計就是一樣的了,所以這種理解是錯誤的,你可以這樣理解:await 等待的過程中,沒有執行緒!!!
再接上面,等待操作完成之後,這時候就會從執行緒池中隨機拿一個執行緒繼續執行,拿到的這個執行緒有可能是 await 操作剛剛釋放掉的,但也有可能是其他執行緒,上圖中的 2-6 操作就是這樣,一圖勝千言:
瞭解了整個過程之後,你才會明白 async await 到底是什麼鬼?以及它真正的用武之地是什麼?簡單總結幾點內容:
- async 非同步網路處理作用最明顯(HttpClient 請求或資料庫連線):這個我們大家都很清楚,也很好理解,如果是其他操作,比如一個非同步方法中你做了很多費時的計算,那這個非同步將沒什麼效果,說白了和同步一樣,而對於網路操作,我們一般不做處理,發起請求之後等待它完成就行,所以這時候執行到這的執行緒,可以釋放並會到執行緒池中,網路操作執行完成之後,再從執行緒池中隨機拿一個執行緒繼續執行。
- async 非同步並不是真正意義上的“非同步”:什麼意思呢?你仔細看下上面測試的輸出結果,會發現 ManagedThreadId1-6 是順序輸出的,而不是先輸出 ManagedThreadId4 再輸出 ManagedThreadID3,所以,非同步和同步的執行過程是一樣的,並且一個請求下,執行時間也是一樣的,上面的非同步測試其實某種意義上,是測試不出任何東西的(從測試結果就可以看出),非同步並不能減少你的執行時間,而是增加你的請求執行數量,這個東西說白了,其實就是併發量。
- async 非同步的精髓是 await:這個之前已經提到了,準確來說,async 非同步的精髓是 await 時的執行緒回收與完成之後的執行緒切換,這個操作最大的價值是,避免執行緒的浪費等待,充分利用執行緒的執行,有點類似於地主不能容忍奴隸閒著做無意義的事,而是希望他們 24 小時不停工作一樣。
另外,在 ASP.NET 應用程式中,我們可以使用 Thread.CurrentThread
來訪問當前的執行執行緒,我之前想做這樣一個測試,讓當前執行執行緒 Sleep 一段時間,看看其他執行緒會不會執行,但 Thread.CurrentThread
並沒有 Sleep 方法,而必須這樣訪問 Thread.Sleep(int millisecondsTimeout)
,如果這樣執行這段程式碼,那麼當前執行緒將會 Sleep,但其他執行緒並不會在它 Sleep 的時候,而繼續執行,為什麼?因為 CPU 在同一時間段內只能執行一個執行緒。
瞭解了 async await 到底是什麼鬼後,博文一開始剩下的兩個有關非同步操作中的異常問題,現在理解起來就非常容易了:
- 非同步操作中發生異常,該如何處理?:和同步一樣處理,同步中報錯,非同步也一樣報錯,有人可能有這樣的疑問,比如測試程式碼中的 Index Action,執行到 await Test 內部操作的時候,突然丟擲異常了,然後就想當然的認為,既然是非同步執行的 Test 方法,那 Index 應該不會影響吧?其實你執行之後就會發現,Index 頁面還是會丟擲異常的,所以異常和非同步沒半毛錢關係。
- 非同步操作中發生異常(有無 catch throw 情況),Application_Error 會不會捕獲?:無 catch,Application_Error 會捕獲;有 catch 無 throw,Application_Error 不會捕獲;有 catch 有 throw,Application_Error 會捕獲。
如果我們想讓某一個非同步方法,在執行丟擲異常的時候,而不影響其他非同步方法,那我們就 catch 而不 throw,比如我們的測試程式碼:
[Route("")] [HttpGet] public async Task<string> Index() { System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId); var result = await Test(); System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId6:" + Thread.CurrentThread.ManagedThreadId); return result; } public static async Task<string> Test() { try { System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId); using (var client = new HttpClient()) { var response = await client.GetAsync("http://stackoverflow.com/questions/33408905/pgadminiii-bug-on-query-tool"); System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId); throw new Exception("test exception");//這裡出現了異常 return await response.Content.ReadAsStringAsync(); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("異常資訊:" + ex.Message); return ""; //throw ex; } }
這樣的效果就是 Index 頁面不會報錯,並且也不會影響其他方法執行,現在發現當時疑惑這個問題的時候,還蠻白痴的,還是那句話,異常和非同步沒半毛錢關係,相同的問題,同步也是這樣進行處理的。
博文內容有點多,如果你不願花時間看,可以直接記住這段話:如果你的應用程式請求訪問很少(併發很小),非同步和同步將是一樣的效果,非同步化改造是毫無意義的,而如果你的應用程式請求訪問很多(併發很大),那麼效果顯而易見,如果使用非同步將會為你省掉幾臺伺服器的錢,但程式碼非同步化並不能使你的應用程式執行速度加快(指的是程式碼執行速度),垃圾程式碼還是垃圾程式碼,並不會有任何的改善,所以,寫好“好的程式碼”很重要!!!
相關文章
- 對於同步、非同步、阻塞、非阻塞的幾點淺薄理解非同步
- Socket程式設計中的同步、非同步、阻塞和非阻塞(轉)程式設計非同步
- 理解結對程式設計程式設計
- python 網路程式設計----非阻塞或非同步程式設計Python程式設計非同步
- [譯] 非同步程式設計:阻塞與非阻塞非同步程式設計
- 對執行緒、協程和同步非同步、阻塞非阻塞的理解執行緒非同步
- 你好,JavaScript非同步程式設計—- 理解JavaScript非同步的美妙JavaScript非同步程式設計
- 你好,JavaScript非同步程式設計---- 理解JavaScript非同步的美妙JavaScript非同步程式設計
- 我對物件導向程式設計的理解,望banq指點。物件程式設計
- 美女程式設計師觀點:程式設計師最重要的非程式設計技巧程式設計師
- 理解阻塞、非阻塞、同步、非同步非同步
- 同步、非同步,阻塞、非阻塞理解非同步
- 簡單理解非同步程式設計(python)和非同步程式設計(nodejs)非同步程式設計PythonNodeJS
- 對Thrift的一點點理解
- 一文徹底搞定(阻塞/非阻塞/同步/非同步)網路IO、併發程式設計模型、非同步程式設計模型的愛恨情仇非同步程式設計模型
- 對程式碼命名的一點思考和理解
- 一個博主對程式設計師和生活的理解程式設計師
- 同步、非同步、阻塞、非阻塞的簡單理解非同步
- 同步與非同步、阻塞與非阻塞的理解非同步
- 談談對不同I/O模型的理解 (阻塞/非阻塞IO,同步/非同步IO)模型非同步
- 我對EVE的一點點理解
- 深入理解nodejs中的非同步程式設計NodeJS非同步程式設計
- 程式設計師對記憶體的理解程式設計師記憶體
- ASP.Net中的async+await非同步程式設計ASP.NETAI非同步程式設計
- 程式設計師的“非程式設計師”之路程式設計師
- 一直讓 PHP 程式設計師懵逼的同步阻塞非同步非阻塞,終於搞明白了PHP程式設計師非同步
- 深入理解 Python 非同步程式設計(上)Python非同步程式設計
- 我對函數語言程式設計的理解函數程式設計
- 對稱加密和非對稱加密(一)初步理解加密
- 說一說javascript的非同步程式設計JavaScript非同步程式設計
- 有關“非計算機專業如何轉行做程式設計師”的一點思考計算機程式設計師
- JS非同步程式設計——深入理解async/awaitJS非同步程式設計AI
- 【進階之路】併發程式設計(三)-非阻塞同步機制程式設計
- 怎樣理解阻塞非阻塞與同步非同步的區別?非同步
- 非同步程式設計:基於事件的非同步程式設計模式(EAP)非同步程式設計事件設計模式
- Java非同步程式設計:CompletableFuture與Future的對比Java非同步程式設計
- python非同步IO程式設計(一)Python非同步程式設計
- 併發程式設計(一)——同步類容器程式設計