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