傳統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 並且 所在線程釋放回線程池了,上下文信息因為沒有被保持下來,就獲取不到了,這時候會拋出異常 未將對象引用設置到對象的實例,經過測試這個異常信息並不一定每次都會出現,原因和線程的釋放有關,調用者所在線程的上下文信息存在就不會拋出異常。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM