ASP.NET 谨用 async/await
C#5.0引入async/await关键字,旨在简化异步编程模型,抛去语法糖就是Net4.0的Task+状态机。其实在处理异步编程使用Task还是挺简单的,不过既然推出了新的语法糖,难免会尝试一下,然而在使用中却没想象中那么单纯。以下针对ASP.NET应用程序实际使用过程中的一些总结,包括异常捕获、死锁、应用程序崩溃,实际使用过程中一不注意就可能掉坑里了。
异常捕获
async方法有三种返回类型:void、Task、Task
asyncvoid
该方式声明的方法是无法使用catch捕获异常的,所以以下代码的try、catch并没什么卵用。
privatestaticasyncvoidThrowExceptionAsync() { awaitTask.Delay(1000); thrownewException("抛个异常玩玩"); } publicstaticasyncvoidCatchAsyncVoidException() { try { ThrowExceptionAsync(); } catch(Exceptionex) { throwex; } }
asyncTask或asyncTask
这两种方式声明的方法异常信息会包含Task属性内,但前提需要在try里面使用await等待。
privatestaticasyncTaskThrowExceptionAsync() { awaitTask.Delay(1000); thrownewException("抛个异常玩玩"); } publicstaticasyncTaskCatchAsyncTaskException() { try { awaitThrowExceptionAsync(); } catch(Exceptionex) { throwex; } } TaskScheduler.UnobservedTaskException
未捕获的Task异常信息可以通过设置全局的TaskScheduler.UnobservedTaskException来记录错误日志,在Global.asax增加如下代码:
voidApplication_Start(objectsender,EventArgse) { //在应用程序启动时运行的代码 TaskScheduler.UnobservedTaskException+=TaskScheduler_UnobservedTaskExceptionException; } voidTaskScheduler_UnobservedTaskExceptionException(objectsender,UnobservedTaskExceptionEventArgse) { if(e.Exception!=null) { //dosomething } }
同步上下文
异步编程必然是关于线程的使用,线程有一个同步上下文的概念,个人认为线程同步上下文是async/await遇到最揪心的问题。在现有项目开发中我们可能想尝试使用async/await,但老代码都是同步方式,这时如果调用一个声明为async的方法,死锁和应用程序崩溃的问题一不小心就可能出现。
注意:控制台程序和.NetCore程序将不会遇到这个问题,它们不需要同步上下文。
死锁
privatestaticasyncTaskXXXAsync() { awaitTask.Delay(1000); //somecode } publicstaticvoidTest() { vartask=XXXAsync(); task.Wait(); }
以上代码很完美的实现了死锁。默认情况下,当Wait()未完成的Task时,会捕获当前线程上下文,在Task完成时使用该上下文恢复方法的执行。当async方法内的await执行完成时,它会尝试获取调用者线程所在的上下文执行方法的剩余部分,但是该上下文已含有一个线程,该线程在等待async方法完成。然后它们相互等待对方,然后就没有然后了,死在那里。
针对死锁问题的解决方式是增加ConfigureAwait(false)
//awaitTask.Delay(1000); awaitTask.Delay(1000).ConfigureAwait(false);//解决死锁
当await等待完成时,它会尝试在线程池上下文中执行async方法的剩余部分,因此就不存在死锁。
应用程序崩溃
测试环境总发现IIS应用程序池总是崩溃,到底是什么原因?当时我们对这个问题也是非常懵逼,代码看上去并没什么明显毛病,试图欺骗自己应该是环境本身的问题吧,但事实上确实是在代码中下了毒。通过各种资料查阅和测试,基本可以断定是同步代码调用异步代码,同步上下文引起的问题。
如果调用一个async方法。如果使用await等待,当前线程立马被释放回线程池,线程的上下文信息会被保存。如果没有使用await(asyncvoid的方法,必然没有办法使用await),调用async方法之后,代码会继续往下执行,执行完成后当前线程被释放回线程池,线程的上下文信息不会被保存。当async中的异步任务执行完成后,会从线程池中获取一个线程继续执行剩余代码,同时会获取当初调用者所在线程的上下文信息(如果当初调用者所在线程没有释放回线程池,上下文信息可以获取到)。那么问题就来了,如果当初调用者没有使用await并且所在线程释放回线程池了,上下文信息因为没有被保持下来,就获取不到了,这时候会抛出异常未将对象引用设置到对象的实例,经过测试这个异常信息并不一定每次都会出现,原因和线程的释放有关,调用者所在线程的上下文信息存在就不会抛出异常。异常错误信息如下,这个异常最终会导致应用程序集停止。
在System.Web.ThreadContext.AssociateWithCurrentThread(BooleansetImpersonationContext) 在System.Web.HttpApplication.OnThreadEnterPrivate(BooleansetImpersonationContext) 在System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossiblyUnderLock(SendOrPostCallbackcallback,Objectstate) 在System.Web.LegacyAspNetSynchronizationContext.CallCallback(SendOrPostCallbackcallback,Objectstate) 在System.Web.LegacyAspNetSynchronizationContext.Post(SendOrPostCallbackcallback,Objectstate) 在System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.PostAction(Objectstate) 在System.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallbackcallback,Objectstate,Task¤tTask) ---引发异常的上一位置中堆栈跟踪的末尾--- 在System.Threading.Tasks.AwaitTaskContinuation.<>c.b__18_0(Objects) 在System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Objectstate) 在System.Threading.ExecutionContext.RunInternal(ExecutionContextexecutionContext,ContextCallbackcallback,Objectstate,BooleanpreserveSyncCtx) 在System.Threading.ExecutionContext.Run(ExecutionContextexecutionContext,ContextCallbackcallback,Objectstate,BooleanpreserveSyncCtx) 在System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 在System.Threading.ThreadPoolWorkQueue.Dispatch() 在System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
针对以上异常我们有什么方式可以解决呢?依然是ConfigureAwait(false),在Task上加上ConfigureAwait(false),此设置代表当async中的异步任务完成后,不读取当时调用它的原线程的上下文信息,而是在线程池上下文中执行async方法的剩余部分。
publicstaticTaskXXXAsync() { awaitTask.Run(()=> { //somecode }).ConfigureAwait(false); }
总结
以上所述是小编给大家介绍的ASP.NET谨用async/await,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!