天天看點

System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰

問題現象

IIS應用程式池崩潰(Crash)的特征如下:

1. 從用戶端看,浏覽器一直處于連接配接狀态,Web伺服器無響應。

2. 從伺服器端看(Windows Server 2008 + IIS 7.0),在事件日志中會出現Event ID為5010的錯誤:

A process serving application pool 'q.cnblogs.com' failed to respond to a ping. The process id was '20080'.

這個錯誤的意思是:IIS檢測到程式池'q.cnblogs.com'無響應。為什麼沒有響應呢?因為程式池'q.cnblogs.com'崩潰了。然後呢?IIS會強制回收應用程式池。

(注:如果在你的Web伺服器的事件日志中出現這個錯誤,一定是某個原因引起了應用程式池崩潰。)

問題原因

我們這次遇到的應用程式池崩潰,是由于在使用System.Threading.Tasks.Task進行異步操作時産生了未處理的異常。

示例代碼如下:

System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰
System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰
System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰
System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰

注:這是一個不需要Callback的異步操作,後續沒有task.wait(或者靜态方法Task.WaitAll或Task.WaitAny)操作。

當時我們釋出程式後,由于Task中代碼産生了異常,整個站點無法正常通路,程式池一直處于“崩潰->回收->崩潰->回收”的循環。

解決方法

捕獲Task中所有代碼的異常,示例代碼如下:

System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰
System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰
System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰
System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰

問題分析

The best option here is to "handle" the exception. 

根據上面的英文,我的了解是:當你建立一個Task,沒有調用過task.Wait()或者沒有擷取它的執行結果,(如果Task中出現了未處理的異常),當這個Task被GC回收時,在GC finalization階段,會讓目前應用程式崩潰。

"Unhandled exceptions that are thrown by user code that is running inside a task are propagated back to the joining thread. ···Exceptions are propagated when you use one of the static or instance Task.Wait or Task(Of TResult).Wait methods···"

翻譯:在一個task中運作的代碼抛出的未處理異常會被回傳給(建立該task的)主線程。···當你調用Task.Wait時,異常才會被回傳(給主線程)。

分析:當我們遇到的情況是沒調用Task.Wait,也就是異常沒有被回傳給主線程。下面的這句就提到了這個:

"If you do not wait on a task that propagates an exception, or access its Exception property, the exception is escalated according to the .NET exception policy when the task is garbage-collected."

譯:如果你在一個task中沒有等待異常被傳播,或者通路它的異步特性,在task被GC回收時,該異常會遵循.NET異常政策被逐漸更新。

分析:逐漸更新的後果就是目前應用程式程序崩潰,對于ASP.NET程式來說,就是應用程式池崩潰。

進一步的解決方法

MSDN上的推薦做法是用Task.ContinueWith觀察Task中抛出的異常并進行處理,示例代碼如下:

System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰
System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰
System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰
System.Threading.Tasks.Task 任務引起的IIS應用程式池崩潰

小結

應用程式池崩潰的原因總結 —— System.Threading.Tasks.Task中的代碼抛出了未處理的異常,由于沒有Task.Wait()操作,異常沒有被回傳給主線程,在GC回收時,發現這個身份不明的異常。然後,這個異常被一級一級上報,直到目前程式程序的最高上司,最高上司為了顧全大局,果然決定與這個異常同歸于盡,也就是讓整個應用程式池崩潰。。。