当前位置: 移动技术网 > IT编程>开发语言>.net > ASP.NET 谨用 async/await

ASP.NET 谨用 async/await

2018年01月25日  | 移动技术网IT编程  | 我要评论

bf419,唐青慧,云浮一中

c# 5.0 引入 async/await 关键字,旨在简化异步编程模型,抛去语法糖就是 net4.0 的 task + 状态机。其实在处理异步编程使用 task 还是挺简单的,不过既然推出了新的语法糖,难免会尝试一下,然而在使用中却没想象中那么单纯。以下针对asp.net 应用程序实际使用过程中的一些总结, 包括 异常捕获 、 死锁 、 应用程序崩溃 ,实际使用过程中一不注意就可能掉坑里了。

异常捕获

async 方法有三种返回类型: void、task、task

async void

该方式声明的方法是无法使用 catch 捕获异常的,所以以下代码的 try、catch 并没什么卵用。

private static async void throwexceptionasync()
{
 await task.delay(1000);
 throw new exception("抛个异常玩玩");
}
public static async void catchasyncvoidexception()
{
 try
 {
  throwexceptionasync();
 }
 catch (exception ex)
 {
 throw ex;
 }
}

async task 或 async task

这两种方式声明的方法异常信息会包含task属性内,但前提需要在try里面使用 await 等待。

private static async task throwexceptionasync()
{
 await task.delay(1000);
 throw new exception("抛个异常玩玩");
}
public static async task catchasynctaskexception()
{
 try
 {
   await throwexceptionasync();
 }
 catch (exception ex)
 {
 throw ex;
 }
}
taskscheduler.unobservedtaskexception

未捕获的 task 异常信息可以通过设置全局的taskscheduler.unobservedtaskexception 来记录错误日志,在 global.asax 增加如下代码:

void application_start(object sender, eventargs e)
{
 // 在应用程序启动时运行的代码
 taskscheduler.unobservedtaskexception += taskscheduler_unobservedtaskexceptionexception;
}
void taskscheduler_unobservedtaskexceptionexception(object sender, unobservedtaskexceptioneventargs e)
{
 if (e.exception != null)
 {
 // do something
 }
}

同步上下文

异步编程必然是关于线程的使用,线程有一个同步上下文的概念,个人认为线程同步上下文是 async/await 遇到最揪心的问题。在现有项目开发中我们可能想尝试使用 async/await,但老代码都是同步方式,这时如果调用一个声明为 async 的方法,死锁和应用程序崩溃的问题一不小心就可能出现。

注意:控制台程序和.net core程序 将不会遇到这个问题,它们不需要同步上下文。

死锁

private static async task xxxasync()
{
 await task.delay(1000);
  // some code
}
public static void test()
{
 var task = xxxasync();
 task.wait();
}

以上代码很完美的实现了死锁。 默认情况下,当 wait() 未完成的 task 时,会捕获当前线程上下文,在 task 完成时使用该上下文恢复方法的执行。 当 async 方法内的 await 执行完成时,它会尝试获取调用者线程所在的上下文执行方法的剩余部分, 但是该上下文已含有一个线程,该线程在等待 async 方法完成。然后它们相互等待对方,然后就没有然后了,死在那里。

针对死锁问题的解决方式是增加 configureawait(false)

// await task.delay(1000);
await task.delay(1000).configureawait(false); // 解决死锁

当 await 等待完成时,它会尝试在线程池上下文中执行 async 方法的剩余部分,因此就不存在死锁。

应用程序崩溃

测试环境总发现iis应用程序池总是崩溃,到底是什么原因?当时我们对这个问题也是非常懵逼,代码看上去并没什么明显毛病,试图欺骗自己应该是环境本身的问题吧,但事实上确实是在代码中下了毒。通过各种资料查阅和测试,基本可以断定是同步代码调用异步代码,同步上下文引起的问题。

如果调用一个 async 方法。如果使用 await 等待,当前线程立马被释放回线程池,线程的上下文信息会被保存。如果没有使用 await(async void 的方法,必然没有办法使用 await),调用 async 方法之后,代码会继续往下执行,执行完成后当前线程被释放回线程池,线程的上下文信息不会被保存。当 async 中的异步任务执行完成后,会从线程池中获取一个线程继续执行剩余代码,同时会获取当初调用者所在线程的上下文信息(如果当初调用者所在线程没有释放回线程池,上下文信息可以获取到)。那么问题就来了,如果当初调用者没有使用 await 并且 所在线程释放回线程池了,上下文信息因为没有被保持下来,就获取不到了,这时候会抛出异常 未将对象引用设置到对象的实例 ,经过测试这个异常信息并不一定每次都会出现,原因和线程的释放有关,调用者所在线程的上下文信息存在就不会抛出异常。异常错误信息如下,这个异常最终会导致应用程序集停止。

在 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.web.legacyaspnetsynchronizationcontext.post(sendorpostcallback callback, object state)
  在 system.threading.tasks.synchronizationcontextawaittaskcontinuation.postaction(object state)
  在 system.threading.tasks.awaittaskcontinuation.runcallback(contextcallback callback, object state, task& currenttask)
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
  在 system.threading.tasks.awaittaskcontinuation.<>c.<throwasyncifnecessary>b__18_0(object s)
  在 system.threading.queueuserworkitemcallback.waitcallback_context(object state)
  在 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()
  在 system.threading._threadpoolwaitcallback.performwaitcallback()

针对以上异常我们有什么方式可以解决呢?依然是configureawait(false),在 task 上加上 configureawait(false),此设置代表当 async 中的异步任务完成后,不读取当时调用它的原线程的上下文信息,而是在线程池上下文中执行 async 方法的剩余部分。

public static task xxxasync()
{
 await task.run(() =>
 {
 // some code
 }).configureawait(false);
}

总结

以上所述是小编给大家介绍的asp.net 谨用 async/await,希望对大家有所帮助

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网