新聞中心
本來這篇博文想探討下異步中的異常操作,但自己在做異步測試的時(shí)候,又對(duì) ASP.NET 異步有了新的認(rèn)識(shí),可以說自己之前對(duì)異步的理解還是有些問題,先列一下這篇博文的三個(gè)解惑點(diǎn):

-
async await 到底是什么鬼???
-
異步操作中發(fā)生異常,該如何處理?
-
異步操作中發(fā)生異常(有無 catch throw 情況),Application_Error 會(huì)不會(huì)捕獲?
之前測試過異步中的同步(很多種情況),這次我們把測試代碼寫更復(fù)雜些(異步中再進(jìn)行異步),代碼如下:
- [Route("")]
- [HttpGet]
- public async Task
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
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
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();
- }
- }
- 輸出結(jié)果(執(zhí)行四次):
- 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
這個(gè)測試方法,我執(zhí)行了無數(shù)次,大致就是上面的四種情況,我當(dāng)時(shí)看到輸出結(jié)果,其實(shí)是很凌亂的,我也大家也一樣,并心里有一些疑問:你這真是異步編程嗎?為啥線程千奇百怪?并且最后那個(gè)還只有一個(gè)線程,這和同步有啥區(qū)別???
針對(duì)上面這個(gè)疑問,我想了很久,并對(duì)自己產(chǎn)生了一些質(zhì)疑的聲音:你每天都在寫 async await 代碼,你真的了解它嗎???然后我又重新找到上面那篇 jesse liu 的博文,反復(fù)讀了很多篇,最后終于有了一些“頓悟”,結(jié)合上面的測試代碼,我大致畫了一張示意圖:
結(jié)合上面的圖,我說一下自己的理解,在做測試的時(shí)候,HttpClient.GetAsync 盡量讓它執(zhí)行時(shí)間長些,比如請(qǐng)求的 URL 可以是 stackoverflow 或 github(原因你懂得!),因?yàn)橛袀€(gè)時(shí)間差,這樣我們可以更好的了解線程的執(zhí)行情況,上面圖中“線程1、線程1x、線程3x、線程4x”等等,這些并 不是不同線程,也就是說線程1有可能等于線程1x或線程3x。。。從上面的輸出結(jié)果就可以看出,用線程x來表示兩個(gè)輸出之間所經(jīng)歷的 await 次數(shù),這就證明了一個(gè)疑惑:await 并不一定會(huì)創(chuàng)建和之前不一樣的線程。
到底什么是異步???我個(gè)人覺得,async 異步是一個(gè)偽概念,await 等待才是精髓,一個(gè)線程可以響應(yīng) 多個(gè)請(qǐng)求,如果是同步編程,一個(gè)線程在處理某一個(gè)請(qǐng)求的時(shí)候阻塞了(比如上面測試代碼中的 HttpClient.GetAsync 網(wǎng)絡(luò)操作),那么這個(gè)線程就會(huì)一直等待它處理,在這個(gè)等待的過程中,那么其他請(qǐng)求就不能再使用這個(gè)線程,又因?yàn)?IIS 線程池中的線程數(shù)量有限,那么同步編程下,高并發(fā)將是一個(gè)頭疼的問題,試想一下,如果線程池中的線程數(shù)量為 100 個(gè),這 100 個(gè)線程在同時(shí)處理 100 個(gè)請(qǐng)求的時(shí)候,都悲催的阻塞掉了,這時(shí)候第 101 個(gè)請(qǐng)求將無法執(zhí)行,那么并發(fā)量就是 100。
接上面,同樣的處理過程,如果是異步編程,那將是什么情況呢?比如一個(gè)線程在處理某一個(gè)請(qǐng)求的時(shí)候,執(zhí)行到 await 操作,那么這個(gè)線程將會(huì)釋放回到線程池,然后進(jìn)行等待,等待的過程中,原來的那個(gè)線程就可以處理其他請(qǐng)求或者這個(gè)請(qǐng)求的其他操作,注意等待并不是線程等 待,而是操作等待,我原來就很不理解這個(gè)地方,如果是線程等待,就表示這個(gè)線程會(huì)一直等待它完成,那和同步編程就是一樣的了,所以這種理解是錯(cuò)誤的,你可 以這樣理解:await 等待的過程中,沒有線程?。?!
再接上面,等待操作完成之后,這時(shí)候就會(huì)從線程池中隨機(jī)拿一個(gè)線程繼續(xù)執(zhí)行,拿到的這個(gè)線程有可能是 await 操作剛剛釋放掉的,但也有可能是其他線程,上圖中的 2-6 操作就是這樣,一圖勝千言:
了解了整個(gè)過程之后,你才會(huì)明白 async await 到底是什么鬼?以及它真正的用武之地是什么?簡單總結(jié)幾點(diǎn)內(nèi)容:
-
async 異步網(wǎng)絡(luò)處理作用最明顯(HttpClient 請(qǐng)求或數(shù)據(jù)庫連接):這個(gè)我們大家都很清楚,也 很好理解,如果是其他操作,比如一個(gè)異步方法中你做了很多費(fèi)時(shí)的計(jì)算,那這個(gè)異步將沒什么效果,說白了和同步一樣,而對(duì)于網(wǎng)絡(luò)操作,我們一般不做處理,發(fā) 起請(qǐng)求之后等待它完成就行,所以這時(shí)候執(zhí)行到這的線程,可以釋放并會(huì)到線程池中,網(wǎng)絡(luò)操作執(zhí)行完成之后,再從線程池中隨機(jī)拿一個(gè)線程繼續(xù)執(zhí)行。
-
async 異步并不是真正意義上的“異步”:什么意思呢?你仔細(xì)看下上面測試的輸出結(jié)果,會(huì)發(fā)現(xiàn) ManagedThreadId1-6 是順序輸出的,而不是先輸出 ManagedThreadId4 再輸出 ManagedThreadID3,所以,異步和同步的執(zhí)行過程是一樣的,并且一個(gè)請(qǐng)求下,執(zhí)行時(shí)間也是一樣的,上面的異步測試其實(shí)某種意義上,是測試不出任何東西的(從測試結(jié)果就可以看出),異步并不能減少你的執(zhí)行時(shí)間,而是增加你的請(qǐng)求執(zhí)行數(shù)量,這個(gè)東西說白了,其實(shí)就是并發(fā)量。
-
async 異步的精髓是 await:這個(gè)之前已經(jīng)提到了,準(zhǔn)確來說,async 異步的精髓是 await 時(shí)的線程回收與完成之后的線程切換,這個(gè)操作最大的價(jià)值是,避免線程的浪費(fèi)等待,充分利用線程的執(zhí)行,有點(diǎn)類似于地主不能容忍奴隸閑著做無意義的事,而是希望他們 24 小時(shí)不停工作一樣。
另外,在 ASP.NET 應(yīng)用程序中,我們可以使用 Thread.CurrentThread 來訪問當(dāng)前的執(zhí)行線程,我之前想做這樣一個(gè)測試,讓當(dāng)前執(zhí)行線程 Sleep 一段時(shí)間,看看其他線程會(huì)不會(huì)執(zhí)行,但 Thread.CurrentThread 并沒有 Sleep 方法,而必須這樣訪問 Thread.Sleep(int millisecondsTimeout),如果這樣執(zhí)行這段代碼,那么當(dāng)前線程將會(huì) Sleep,但其他線程并不會(huì)在它 Sleep 的時(shí)候,而繼續(xù)執(zhí)行,為什么?因?yàn)?nbsp;CPU 在同一時(shí)間段內(nèi)只能執(zhí)行一個(gè)線程。
了解了 async await 到底是什么鬼后,博文一開始剩下的兩個(gè)有關(guān)異步操作中的異常問題,現(xiàn)在理解起來就非常容易了:
-
異步操作中發(fā)生異常,該如何處理?:和同步一樣處理,同步中報(bào)錯(cuò),異步也一樣報(bào)錯(cuò),有人可能有這樣的疑問,比 如測試代碼中的 Index Action,執(zhí)行到 await Test 內(nèi)部操作的時(shí)候,突然拋出異常了,然后就想當(dāng)然的認(rèn)為,既然是異步執(zhí)行的 Test 方法,那 Index 應(yīng)該不會(huì)影響吧?其實(shí)你執(zhí)行之后就會(huì)發(fā)現(xiàn),Index 頁面還是會(huì)拋出異常的,所以異常和異步?jīng)]半毛錢關(guān)系。
-
異步操作中發(fā)生異常(有無 catch throw 情況),Application_Error 會(huì)不會(huì)捕獲?:無 catch,Application_Error 會(huì)捕獲;有 catch 無 throw,Application_Error 不會(huì)捕獲;有 catch 有 throw,Application_Error 會(huì)捕獲。
如果我們想讓某一個(gè)異步方法,在執(zhí)行拋出異常的時(shí)候,而不影響其他異步方法,那我們就 catch 而不 throw,比如我們的測試代碼:
- [Route("")]
- [HttpGet]
- public async Task
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
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");//這里出現(xiàn)了異常
- return await response.Content.ReadAsStringAsync();
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine("異常信息:" + ex.Message);
- return "";
- //throw ex;
- }
- }
這樣的效果就是 Index 頁面不會(huì)報(bào)錯(cuò),并且也不會(huì)影響其他方法執(zhí)行,現(xiàn)在發(fā)現(xiàn)當(dāng)時(shí)疑惑這個(gè)問題的時(shí)候,還蠻白癡的,還是那句話,異常和異步?jīng)]半毛錢關(guān)系,相同的問題,同步也是這樣進(jìn)行處理的。
博文內(nèi)容有點(diǎn)多,如果你不愿花時(shí)間看,可以直接記住這段話:如果你的應(yīng)用程序請(qǐng)求訪問很少(并發(fā)很?。?,異步和同步將是一樣的效 果,異步化改造是毫無意義的,而如果你的應(yīng)用程序請(qǐng)求訪問很多(并發(fā)很大),那么效果顯而易見,如果使用異步將會(huì)為你省掉幾臺(tái)服務(wù)器的錢,但代碼異步化并 不能使你的應(yīng)用程序執(zhí)行速度加快(指的是代碼執(zhí)行速度),垃圾代碼還是垃圾代碼,并不會(huì)有任何的改善,所以,寫好“好的代碼”很重要?。?!
分享標(biāo)題:對(duì)ASP.NET異步編程的一點(diǎn)理解
當(dāng)前地址:http://www.dlmjj.cn/article/dhcicih.html


咨詢
建站咨詢
