C#~非同步程式設計再續~await與async引起的w3wp.exe崩潰

張佔嶺發表於2016-05-10

返回目錄

最近怪事又開始發生了,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崩潰中所學習到的知識!

感謝各位的閱讀!

 返回目錄

相關文章