• 传统asp.net小心 async/await坑


    最近在改老项目时,干了一件自以为很有成就感的事,心想 “项目都是同步方法,为啥不用异步方法呢?”,于是有了异步方法,类型下面的代码(当然是举例子说明啊)

    //更新某人名下公司名称
    public Task<bool> UpdateUser(string id,string companyName)
    {
       var usrInfo=Db.GetUsrInfo(id);
    
       var  flag= await Db.UpdateCompanyNameAsync(usrInfo.companyId,companyName);
    
       return flag        
    }

    “咋一看,好像没啥问题,不就是根据id更新名称吗?”

    可实际在测试的时候,报错了,类型下面的错误

    在 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()

    注意:这个错误,在异步方法里用了同步的方法导致的。

    有同学此时可能会有疑问,"这个为啥会报这种错误呢"? 

    别急,这个就涉及到了 “同步上下文”

    同步上下文

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

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

    死锁
    private static async Task TestAsync()
    {
        await Task.Delay(1000);
        // 业务代码
    }
    
    public static void TestOne()
    {
        var task = TestAsync();
        task.Wait();
    }

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

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

    // await Task.Delay(1000);
    await Task.Delay(1000).ConfigureAwait(false);  // 解决死锁

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

           如果项目中,有同步代码,有有很多的异步代码,执行异步代码时的参数是通过同步代码所获取的,那么项目中很有可能会有上述的异常信息

    经过查阅资料,和查看园子里其他大佬们的文章了解到

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

    参考

  • 相关阅读:
    SpringMVC 使用JSR-303进行校验 @Valid
    Hibernate Tools生成注释
    大型网站架构演变和知识体系(转载)
    eclipse从数据库逆向生成Hibernate实体类
    性能测试公众号,欢迎你的加入~
    mysql使用druid监控配置
    (转)面试为什么需要了解JVM
    (转)什么是缓存击穿?
    Mysql推荐使用规范(转)
    java应用监控工具
  • 原文地址:https://www.cnblogs.com/DanielYao/p/10263373.html
Copyright © 2020-2023  润新知