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回收时,发现这个身份不明的异常。然后,这个异常被一级一级上报,直到当前程序进程的最高领导,最高领导为了顾全大局,果然决定与这个异常同归于尽,也就是让整个应用程序池崩溃。。。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏更流畅、简洁的软件开发方式

一个保存数据的方法(可以切换存放的位置,可以设置密钥)

      在asp.net里面一般的生命周期都比较短,如果想要比较长久的保存数据的话,一般有选择几种方式可供选择,即cookies、ViewState、Ses...

289100
来自专栏ZKEASOFT

纸壳CMS的插件加载机制

纸壳CMS是基于插件化设计的,可以通过扩展插件来实现不同的功能。如何通过插件来扩展,可以参考这篇文章:

12820
来自专栏技术记录

RabbitMQ教程(二) ——linux下安装rabbitmq

21140
来自专栏王磊的博客

缓存通用管理类 + 缓存 HttpContext.Current.Cache 和 HttpRuntime.Cache 的区别

  以前写asp.net时用HttpContext.Current.Cache存缓存很好用,今天写了一个windows服务程序,HttpContext.Curr...

39170
来自专栏王磊的博客

ASP.NET 缓存 Cache

ASP.NET 提供一个功能完整的缓存引擎,页面可使用该引擎通过 HTTP 请求存储和检索任意对象. 缓存的生存期与应用程序的生存期相同,也就是说,当应用程序重...

45550
来自专栏古时的风筝

ASP.NET-自定义HttpModule与HttpHandler

在之前的ASP.NET是如何在IIS下工作的这篇文章中介绍了ASP.NET与IIS配合工作的机制,在http请求经过一系列处理后,最后到达ASP.NET管道中...

31880
来自专栏GreenLeaves

WCF系列教程之消息交换模式之请求与答复模式(Request/Reply)

1、使用WCF请求与答复模式须知 (1)、客户端调用WCF服务端需要等待服务端的返回,即使返回类型是void (2)、相比Duplex来讲,这种模式强调的是客户...

234100
来自专栏用户2442861的专栏

linux下jsoncpp的编译和测试使用

http://blog.csdn.net/ljp1919/article/details/48319779

64330
来自专栏跟着阿笨一起玩NET

无法将类型“System.Collections.Generic.IEnumerable<EmailSystem.Model.TemplateInfo>”隐式转换为“System.Collection

33610
来自专栏kwcode

ASP.NET 实现Base64文件流下载PDF

因为业务需要调用接口获取的是 Base64文件流 需要提供给客户下载PDF文档 源码部分借鉴网上,具体地址忘记了。 //Base64文件流 ...

45950

扫码关注云+社区

领取腾讯云代金券