C#~异步编程再续~await与async引起的w3wp.exe崩溃
最近怪事又开始发生了,IIS的应用程序池无做挂掉,都指向同一个矛头,async,threadPool,Task,还有一个System.NullReferenceException,所以这些都让我们感觉,我们的异步程序出现了问题,事实也是如此,我们的异步调用引用了对“上下文”的非空引用,最后导致w3wp进程死掉!
通过其它前辈的分享,找到了问题产生的原因,大叔也总结一下
1 async方法需要使用await等待它的结果,这样可以保证你的SynchronizationContext上下文不为空,即不会出现非空引用的错误。
2 在调用async方法时,如果不方法加await关键字,也可以使用它的ConfigureAwait(false)方法,它虽然不会保存SynchronizationContext上下文,但它也不会报非空引用的错误。
3 在一个新线程里调用async的异步方法,需要我们注意上面两点
参看文章
http://www.cnblogs.com/cmt/p/configure_await_false.html
http://www.cnblogs.com/cmt/p/sokcet_memory_leak.html
技术点说明
1 Task.Run(()=>{}); 将一个任务添加到线程池里,排队执行
2 async 标识一个方法为异步方法,可以与主线程并行执行,发挥CPU的多核优势
3 await 在调用一个async方法前可以添加这个修饰符,它意思是等待当前异步方法执行完后,再执行下面的代码
4 ConfigureAwait(true),代码由同步执行进入异步执行时,当前线程上下文信息就会被捕获并保存至 SynchronizationContext中,供异步执行中使用,并且供异步执行完成之后的同步执行中使用
5 Configurewait(flase),不进行线程上下文信息的捕获,async方法中与await之后的代码执行时就无法获取await之前的线程的上下文信息,在ASP.NET中最直接的影响就是HttpConext.Current的值为null,但不会出现非空引用的错误
Async引起的死锁,w3wp.exe挂的原因
对于将异步方法偷懒的人,即使用Wait()和Result的人,将会为些付出代价,因为它会引起线程的死锁,最终导致w3wp挂掉,注意在控制器console程序中,这件事不会发生。
MSDN:https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx
始终使用 Async
异步代码让我想起了一个故事,有个人提出世界是悬浮在太空中的,但是一个老妇人立即提出质疑,她声称世界位于一个巨大乌龟的背上。 当这个人问乌龟站在哪里时,老夫人回答:“很聪明,年轻人,下面是一连串的乌龟!”在将同步代码转换为异步代码时,您会发现,如果异步代码调用其他异步代码并且被其他异步代码所调用,则效果最好 — 一路向下(或者也可以说“向上”)。 其他人已注意到异步编程的传播行为,并将其称为“传染”或将其与僵尸病毒进行比较。 无论是乌龟还是僵尸,无可置疑的是,异步代码趋向于推动周围的代码也成为异步代码。 此行为是所有类型的异步编程中所固有的,而不仅仅是新 async/await 关键字。
“始终异步”表示,在未慎重考虑后果的情况下,不应混合使用同步和异步代码。 具体而言,通过调用 Task.Wait 或 Task.Result 在异步代码上进行阻塞通常很糟糕。 对于在异步编程方面“浅尝辄止”的程序员,这是个特别常见的问题,他们仅仅转换一小部分应用程序,并采用同步 API 包装它,以便代码更改与应用程序的其余部分隔离。 不幸的是,他们会遇到与死锁有关的问题。 在 MSDN 论坛、Stack Overflow 和电子邮件中回答了许多与异步相关的问题之后,我可以说,迄今为止,这是异步初学者在了解基础知识之后最常提问的问题: “为何我的部分异步代码死锁?”
其中一个方法发生阻塞,等待 async 方法的结果。 此代码仅在控制台应用程序中工作良好,但是在从 GUI 或 ASP.NET 上下文调用时会死锁。 此行为可能会令人困惑,尤其是通过调试程序单步执行时,这意味着没完没了的等待。 在调用 Task.Wait 时,导致死锁的实际原因在调用堆栈中上移。
这种死锁的根本原因是 await 处理上下文的方式。 默认情况下,当等待未完成的 Task 时,会捕获当前“上下文”,在 Task 完成时使用该上下文恢复方法的执行。 此“上下文”是当前 SynchronizationContext(除非它是 null,这种情况下则为当前 TaskScheduler)。 GUI 和 ASP.NET 应用程序具有 SynchronizationContext,它每次仅允许一个代码区块运行。 当 await 完成时,它会尝试在捕获的上下文中执行 async 方法的剩余部分。 但是该上下文已含有一个线程,该线程在(同步)等待 async 方法完成。 它们相互等待对方,从而导致死锁。
请注意,控制台应用程序不会形成这种死锁。 它们具有线程池 SynchronizationContext 而不是每次执行一个区块的 SynchronizationContext,因此当 await 完成时,它会在线程池线程上安排 async 方法的剩余部分。 该方法能够完成,并完成其返回任务,因此不存在死锁。 当程序员编写测试控制台程序,观察到部分异步代码按预期方式工作,然后将相同代码移动到 GUI 或 ASP.NET 应用程序中会发生死锁,此行为差异可能会令人困惑。
此问题的最佳解决方案是允许异步代码通过基本代码自然扩展。 如果采用此解决方案,则会看到异步代码扩展到其入口点(通常是事件处理程序或控制器操作)。 控制台应用程序不能完全采用此解决方案,因为 Main 方法不能是 async。 如果 Main 方法是 async,则可能会在完成之前返回,从而导致程序结束。 控制台应用程序的 Main 方法是代码可以在异步方法上阻塞为数不多的几种情况之一。
代码如下
public class tools { public static async Task TestAsync() { await Task.Delay(1000); } } public class HomeController : Controller { public ActionResult Index() { tools.TestAsync().Wait();//产生死锁,w3wp.exe挂掉 ViewBag.Message = "test"; return View(); } }
在Task.Delay(1000)后面添加Configurewait(flase)可以有效的避免代码的死锁!( 此时,当等待完成时,它会尝试在线程池上下文中执行 async 方法的剩余部分。 该方法能够完成,并完成其返回任务,因此不存在死锁。 如果需要逐渐将应用程序从同步转换为异步,则此方法会特别有用。)
以上就是我们在解决由异步引起的w3wp.exe崩溃中所学习到的知识!
感谢各位的阅读!