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進行異步操作時產生了未處理的異常。

示例代碼如下:

Task.Factory.StartNew(() =>
{
//下面的代碼未用try..catch捕獲異常
//...
});

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

當時我們發布程序后,由於Task中代碼產生了異常,整個站點無法正常訪問,程序池一直處於“崩潰->回收->崩潰->回收”的循環。

解決方法

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

Task.Factory.StartNew(() =>
{
try
{
//...
}
catch { }
});

問題分析

在stackoverflow上提到了這個問題的原因

If you create a Task, and you don't ever call task.Wait() or try to retrieve the result of a Task<T>, when the task is collected by the garbage collector, it will tear down your application during finalization. For details, see MSDN's page on Exception Handling in the TPL.

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

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

進一步看MSDN中的Exception Handling (Task Parallel Library)

"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中拋出的異常並進行處理,示例代碼如下:

var task1 = Task.Factory.StartNew(() =>
{
throw new MyCustomException("Task1 faulted.");
})
.ContinueWith((t) =>
{
Console.WriteLine("I have observed a {0}",
t.Exception.InnerException.GetType().Name);
},
TaskContinuationOptions.OnlyOnFaulted);

小結

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM