水火难容:同步方法调用async方法引发的ASP.NET应用程序崩溃
之前只知道在同步方法中调用异步(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应用程序崩溃的更多相关文章
- VS2010 F5调试时出现:“ 尝试运行项目时出错:未捕获通过反射调用的方法引发的异常”解决
VS2010 F5调试时出现 尝试运行项目时出错:未捕获通过反射调用的方法引发的异常 两个解决方法:1) 打开项目属性,选择调试选项卡,将“启用非托管代码调试”一项钩上.2) 打开项目属性,选择调试选 ...
- ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法
问题 首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁.例: public ActionResult Asv2() { //dead lock var t ...
- MVC 如何在一个同步方法(非async)方法中等待async方法
MVC 如何在一个同步方法(非async)方法中等待async方法 问题 首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁.例: public Actio ...
- 【Crash】C++程序崩溃排查方法
windows下C++程序release版本崩溃错误排查方法. 一个你精心设计的24小时不间断运行,多线程的程序,突然运行了几个月后崩了,此问题是非常难以排查的,也是很头疼的问题. 现利用Google ...
- [置顶] Ajax程序:处理异步调用中的异常(使用Asp.Net Ajax内建的异常处理方法)
无论在Window应用程序,还是Web应用程序以对用户友好的方式显示运行时的异常都是很有必要,尤其对于可能有很多不确定因素导致异常的Web应用程序;在传统的Web开发中,处理异常的方式——设计专门一个 ...
- Asp.Net Core SignalR 用泛型Hub优雅的调用前端方法及传参
继续学习 最近一直在使用Asp.Net Core SignalR(下面成SignalR Core)为小程序提供websocket支持,前端时间也发了一个学习笔记,在使用过程中稍微看了下它的源码,不得不 ...
- C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理
C#编译器优化那点事 使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...
- 【问题】Asp.net MVC 的cshtml页面中调用JS方法传递字符串变量参数
[问题]Asp.net MVC 的cshtml页面中调用JS方法传递字符串变量参数. [解决]直接对变量加引号,如: <button onclick="deleteProduct('@ ...
- asp.net如何在前台利用jquery Ajax调用后台方法
一 :最近因为帮同事开发项目使用到了asp.net,而我又想实现Ajax异步请求....从网上查询了一下资料之后,原来在asp.net中利用Ajax调用后台方法同样很简单,为了便于自己以后查看,特将此 ...
随机推荐
- mysql的从头到脚优化之数据库引擎的选择(转载)
一. Mysql常用的存储引擎包括Innodb和Myisam以及memory引擎,但是最常用的莫过于Innodb引擎和MyISAM引擎,下边分别做下记录和比较: 下面思考下这几个问题: 你的数据库需要 ...
- linux查看访问windows共享目录NT_STATUS_DUPLICATE_NAME问题解决
linux查看访问windows共享目录NT_STATUS_DUPLICATE_NAME问题解决 [jason@superfreak ~]$ smbclient //powerhouse-smb.my ...
- CSS样式的插入方式
1.外部样式: 当样式需要应用于很多页面时,外部样式表将是理想的选择.<head> <link rel="stylesheet" type="text/ ...
- mysql 不允许连接
错误提示: ERROR 1130: Host '192.168.1.1' is not allowed to connect to this MySQL server的解决方法: 1.改表法.可能是你 ...
- codeforces 361 B - Mike and Shortcuts
原题: Description Recently, Mike was very busy with studying for exams and contests. Now he is going t ...
- 升级 DNX 和 DNVM
升级命令: dnvm upgrade -u dnvm upgrade -u –runtime CoreCLR -u 表示 unstable(不稳定),不带 -u 表示升级到最新稳定(stable)版本 ...
- XML团队介绍发布!
三个臭皮匠,赛过诸葛亮 /********************************************************************/ 成员: 姓名:项浩哲 职务:项目经 ...
- HTML第二天
针对html做了一个知识点的思维导图
- Centos上DNS服务器的简单搭建
1:安装软件包 yum -y install bind bind-chroot bind-utils bind-libs 2:修改配置文件 1): vim /etc/named.conf 2):在主 ...
- notepad++对systemverilog的支持
找到notepad++根目录中的"langs.xml",用notepad++打开,并搜索"verilog", 找到后,修改后面那句话为ext=" ...