最近在改老項目時,幹了一件自覺得頗有成就感的事,心想 「項目都是同步方法,爲啥不用異步方法呢?」,因而有了異步方法,類型下面的代碼(固然是舉例子說明啊)html
//更新某人名下公司名稱 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
有同窗此時可能會有疑問,"這個爲啥會報這種錯誤呢"? 異步編程
別急,這個就涉及到了 「同步上下文」測試
異步編程必然是關於線程的使用,線程有一個同步上下文的概念,我的認爲線程同步上下文是 async/await 遇到最揪心的問題。在現有項目開發中咱們可能想嘗試使用 async/await,但老代碼都是同步方式,這時若是調用一個聲明爲 async 的方法,死鎖和應用程序崩潰的問題一不當心就可能出現。spa
注意: 控制檯程序和.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 方法完成。而後它們相互等待對方,而後就沒有而後了,死在那裏。code
針對死鎖問題的解決方式是增長 ConfigureAwait(false)orm
// await Task.Delay(1000); await Task.Delay(1000).ConfigureAwait(false); // 解決死鎖
當 await 等待完成時,它會嘗試在線程池上下文中執行 async 方法的剩餘部分,所以就不存在死鎖。
若是項目中,有同步代碼,有有不少的異步代碼,執行異步代碼時的參數是經過同步代碼所獲取的,那麼項目中頗有可能會有上述的異常信息
通過查閱資料,和查看園子裏其餘大佬們的文章瞭解到
當調用一個 async 方法。若是使用 await 關鍵字,當前線程立馬被釋放回線程池,線程的上下文信息會被保存。若是沒有使用 await(async void 的方法,必然沒有辦法使用 await),調用 async 方法以後,代碼會繼續往下執行,執行完成後當前線程被釋放回線程池,線程的上下文信息不會被保存。當 async 中的異步任務執行完成後,會從線程池中獲取一個線程繼續執行剩餘代碼,同時會獲取當初調用者所在線程的上下文信息(若是當初調用者所在線程沒有釋放回線程池,上下文信息能夠獲取到)。那麼問題就來了,若是當初調用者沒有使用 await 而且 所在線程釋放回線程池了,上下文信息由於沒有被保持下來,就獲取不到了,這時候會拋出異常 未將對象引用設置到對象的實例,通過測試這個異常信息並不必定每次都會出現,緣由和線程的釋放有關,調用者所在線程的上下文信息存在就不會拋出異常。