之前只知道在同步方法中调用异步(async)方法时,如果用.Result等待调用结果,会造成线程死锁(deadlock)。自己也吃过这个苦头,详见等到花儿也谢了的await

昨天一个偶然的情况,造成在同步方法中调用了async方法,并且没有使用.Result,结果造成整个ASP.NET应用程序的崩溃,见识了同步/异步水火难容的厉害。

当时的情况是这样的,发布了一个经过异步化改造的ASP.NET程序,其中有这样一个同步方法:

public static void Notify(string title, string content, int recipientId)
{
//...
}

被改造为异步方法:

public static async Task Notify(string title, string content, int recipientId)
{
//await ...
}

之前在WebForms(.aspx)中是这样同步调用它的:

<script runat="server">
void Page_Load(Object sender, EventArgs e)
{
//...
MsgService.Notify(title, body, userId);
//...
}
</script>

现在改为在MVC Controller Action中异步调用它:

public class ApplyController : Controller
{
[HttpPost]
public async Task<string> Pass()
{
//...
await MsgService.Notify(title, body, userId);
//...
}
}

这次发布就是为了用MVC取代WebForms,但发布时同步调用Notify()方法的.aspx文件没有从服务器上删除。

发布后,这个ASP.NET程序跑一会就崩溃(crash),具体表现为:

a)访问网站出现503错误;

b)IIS管理器中显示对应的应用程序池处于停止状态;

c)在Windows事件日志中发现以下三个错误:

日志1:

发生了未经处理的异常,已终止进程。
Application ID: /LM/W3SVC/15/ROOT
Process ID: 23808
Exception: System.NullReferenceException
Message: 未将对象引用设置到对象的实例。 StackTrace:
在 System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
在 System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
在 System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state)
在 System.Web.LegacyAspNetSynchronizationContext.CallCallback(SendOrPostCallback callback, Object state)
在 System.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallback callback, Object state, Task& currentTask)
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
在 System.Threading.Tasks.AwaitTaskContinuation.<ThrowAsyncIfNecessary>b__1(Object s)
在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
在 System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
在 System.Threading.ThreadPoolWorkQueue.Dispatch()

日志2:

应用程序: w3wp.exe
Framework 版本: v4.0.30319
说明: 由于未经处理的异常,进程终止。
异常信息: System.NullReferenceException
堆栈:
在 System.Threading.Tasks.AwaitTaskContinuation.<ThrowAsyncIfNecessary>b__1(System.Object)
在 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
在 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
在 System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
在 System.Threading.ThreadPoolWorkQueue.Dispatch()

日志3:

Faulting application name: w3wp.exe, version: 7.5.7601.17514, time stamp: 0x4ce7afa2
Faulting module name: KERNELBASE.dll, version: 6.1.7601.18798, time stamp: 0x5507b87a
Exception code: 0xe0434352
Fault offset: 0x000000000001aaad
Faulting process id: 0x5d00
Faulting application start time: 0x01d0b86f3af9058e
Faulting application path: c:\windows\system32\inetsrv\w3wp.exe
Faulting module path: C:\Windows\system32\KERNELBASE.dll
Report Id: 7bec0e6c-2462-11e5-b24e-c43d8baaa802

从日志信息看,问题肯定是异步引起的,于是检查所有进行异步调用的代码,没发现问题(唯独没有检查那个以为不在使用、没有删除的.aspx文件)。

后来才想到那个没有删除的.aspx文件,可是它已经被MVC取代了,没在使用啊。如果是它引起的,只有一个可能。。。这个文件依然在被某些请求访问。仔细排查后发现原来是引用js的地方没加hash参数,造成有些客户端浏览器由于缓存的原因还在使用旧版的js,旧版的js还会向这个.aspx文件发出ajax请求。

原来是一个疏忽造成了在同步方法中直接调用异步方法,但怎么也没想到竟然有如此大的威力,能引起整个应用程序的崩溃,于是好奇心被激发。

看了网上的一些资料后,对这个问题有了一些认识。

在ASP.NET中(ASP.NET天生是多线程的,基于线程池的,没有UI线程的概念),如果你调用了一个async方法,如果有await相伴,当前线程立马被释放回线程池,线程的上下文信息(比如reqeust context)被保存;如果没有await相伴(也没有其他的wait代码),调用async方法之后,代码会继续往下执行,直至完成,当前线程被释放回线程池,线程的上下文信息不会被保存。当async中的异步任务完成后(注:异步任务不是在另外一个线程中完成的,是在一个状态机中完成的),会从线程池中取出一个线程继续执行,执行时会读取当时调用它的原线程的上下文信息(默认情况下的行为,如果ConfigureAwait(false) ,就没有这一步操作),如果当初调用时没有使用await,线程的上下文信息没有被保存,这时就会引发NullReferenceException。而在这种级别发生的未处理null引用异常,会引发整个应用程序崩溃,更准确地说是应用程序所在的进程崩溃。因为这样的异常实在太危险,为了不让一只老鼠坏了一锅汤,只能被牺牲。

所以,如果不想被牺牲,要么老老实实地await;要么告诉async方法,不要读取原线程的上下文信息(ConfigureAwait(false),未经实际验证是否有效);要么调用async方法的线程没有需要保存的上下文信息,比如在Task.Run(或Task.Factory.StartNew)中调用async方法,也就是用一个新的线程调用async方法。

【推荐阅读】

Best practice to call ConfigureAwait for all server-side code

Difference between the TPL & async/await (Thread handling)

Does an async void method create a new thread everytime it is called?

水火难容:同步方法调用async方法引发的ASP.NET应用程序崩溃的更多相关文章

  1. VS2010 F5调试时出现:“ 尝试运行项目时出错:未捕获通过反射调用的方法引发的异常”解决

    VS2010 F5调试时出现 尝试运行项目时出错:未捕获通过反射调用的方法引发的异常 两个解决方法:1) 打开项目属性,选择调试选项卡,将“启用非托管代码调试”一项钩上.2) 打开项目属性,选择调试选 ...

  2. ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法

    问题 首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁.例: public ActionResult Asv2() { //dead lock var t ...

  3. MVC 如何在一个同步方法(非async)方法中等待async方法

    MVC 如何在一个同步方法(非async)方法中等待async方法 问题 首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁.例: public Actio ...

  4. 【Crash】C++程序崩溃排查方法

    windows下C++程序release版本崩溃错误排查方法. 一个你精心设计的24小时不间断运行,多线程的程序,突然运行了几个月后崩了,此问题是非常难以排查的,也是很头疼的问题. 现利用Google ...

  5. [置顶] Ajax程序:处理异步调用中的异常(使用Asp.Net Ajax内建的异常处理方法)

    无论在Window应用程序,还是Web应用程序以对用户友好的方式显示运行时的异常都是很有必要,尤其对于可能有很多不确定因素导致异常的Web应用程序;在传统的Web开发中,处理异常的方式——设计专门一个 ...

  6. Asp.Net Core SignalR 用泛型Hub优雅的调用前端方法及传参

    继续学习 最近一直在使用Asp.Net Core SignalR(下面成SignalR Core)为小程序提供websocket支持,前端时间也发了一个学习笔记,在使用过程中稍微看了下它的源码,不得不 ...

  7. C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理

    C#编译器优化那点事   使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...

  8. 【问题】Asp.net MVC 的cshtml页面中调用JS方法传递字符串变量参数

    [问题]Asp.net MVC 的cshtml页面中调用JS方法传递字符串变量参数. [解决]直接对变量加引号,如: <button onclick="deleteProduct('@ ...

  9. asp.net如何在前台利用jquery Ajax调用后台方法

    一 :最近因为帮同事开发项目使用到了asp.net,而我又想实现Ajax异步请求....从网上查询了一下资料之后,原来在asp.net中利用Ajax调用后台方法同样很简单,为了便于自己以后查看,特将此 ...

随机推荐

  1. ubuntu 挂载windows共享目录的方法

    建立windows共享目录 右击要共享的文件夹-->属性-->高级共享-->添加用户-->添加完全控制权限 假设建立的共享地址为\\192.168.1.100\Linux 获取 ...

  2. 7.Mybatis关联表查询(这里主要讲的是一对一和一对多的关联查询)

    在Mybatis中的管理表查询这里主要介绍的是一对一和一对多的关联查询的resultMap的管理配置查询,当然你也可以用包装类来实现.不过这里不说,做关联查询的步骤可以简单的总结为以下的几步: 1.分 ...

  3. Node中的http模块

    通过Node模块,我们可以实现客户端和服务器端.这篇文章主要研究如何利用http和一些相关模块构建客户端和服务器端代码.读完本文,将能够实现client向server发送数据,而server将数据原样 ...

  4. Dx unsupported class file version 52.0

    最近用ADT时遇到这个bug,折腾了好几天. 报错信息: Dx unsupported class file version 52.0 Conversion to Dalvik format fail ...

  5. [jQuery]最新的 3.0 已发布

    可以从 https://code.jquery.com/jquery/#jquery-all-1.x 找到历史版本 1.12.4 是支持 IE6 - 8 的最高版本. 下载地址 http://file ...

  6. [转]Excel 取汉字拼音首位

    转自:http://jingyan.baidu.com/article/63acb44adca44461fcc17e85.html 转自:http://jingyan.baidu.com/articl ...

  7. oracle创建用户、授予权限及删除用户

    创建用户   oracle对表空间 USERS 无权限 alter user 用户名 quota unlimited on users;   //创建临时表空间 create temporary ta ...

  8. strace命令介绍(转)

    原文链接:http://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316692.html 简介 strace常用来跟踪进程执行时的系统调用和所接收的信 ...

  9. const限定符

    1 const的作用 便于进行类型检查.可以保护被修饰的东西.避免不必要的内存分配.为函数重载提供一个参考. 2 const成员函数 const成员函数只能访问数据成员的值,而不能修改他. #incl ...

  10. jquery实现标签上移、下移、置顶

    eg:如在后台的标签列表中,实现上移.下移.置顶功能 思路: 1.先用到的克隆方法.clone(true): 即把当前要移动的项先保存好,备于后用. 2.找到当前标签所对应的相关元素及其相关方法: 如 ...