task async await 異常處理
阿新 • • 發佈:2022-04-02
今天跟前端對接支付介面碰到一個奇葩問題,支付介面報錯了,但是返回到前端的結果還是成功的,這裡簡單說一下整個呼叫鏈路,前端調我的api介面,我再調其他服務,其他服務再調同事的介面,最後呼叫同事的底層支付介面(這部分程式碼我沒有),跟蹤來跟蹤去還是沒發現問題,最後要同事確認下,底層的支付介面有沒有給我丟擲異常,同事很肯定的說返回了異常出來,最後仔細跟蹤倒數第二個介面發現了問題,
以上程式碼不是原始程式碼,但是跟這份程式碼結構類似,乍一看這程式碼確實沒啥問題,但是上面的那個異常死活拋不出來,我們看下這兩個方法,都是async方法,看似跟普通同步方法沒啥區別,其實底層執行邏輯區別很大,接著以前il程式碼的分析,被async修飾的方法會被編譯器編譯成class,並且實現狀態機介面,最後我們的test方法裡面的內容在狀態機movenext方法裡面被呼叫,看下il程式碼
大概長上面那樣,發現問題沒?test1方法裡面的throw被編譯器暫時消化掉了,編譯器編譯時會預設加上try catch,那這個怎麼處理,我task裡面的異常怎麼拿到?兩種方案,要麼await表示式,要麼呼叫同步方法wait(),正確操作應該是
那麼這有啥本質區別呢?首先編譯器會把await表示式前後定義兩種狀態邏輯,後面的邏輯會被阻塞住(這裡的阻塞可能是執行緒),並且hook表示式await的結果result,當hook到任務task已完成,jit會呼叫getResult()方法獲取結果,這裡有個細節需要注意了,如果task在執行過程中有異常,這時候它才會跑出來,我們可以簡單看下GetResult()方法的實現。
public async Task<object> test() { test1(); return "xxx"; } private async Task test1() { var response = A.底層支付介面(); if (response.code != 200) { throw new Exception(); } await Task.Run(...); }
.try { ...... Monix.Wallet.Service.Controllers.PaymentDeskController Monix.Wallet.Service.Controllers.PaymentDeskController/'<test2>d__4'::'<>4__this' IL_001e: call instance class [mscorlib]System.Threading.Tasks.Task Monix.Wallet.Service.Controllers.PaymentDeskController::test22() IL_0034: ldnull IL_0035: stloc.1 // V_1 IL_0036: leave.s IL_0050 } // end of .try catch [mscorlib]System.Exception { ... valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<object>::SetException(class [mscorlib]System.Exception) IL_004d: nop IL_004e: leave.s IL_0065 } // end of catch .... } // end of method '<test2>d__4'::MoveNext
public async Task<object> test() { await test1(); return "xxx"; } private async Task test1() { var response = A.底層支付介面(); if (response.code != 200) { throw new Exception(); } await Task.Run(...); }
private static void ThrowForNonSuccess(Task task) { .... switch (task.Status) { .... case TaskStatus.Faulted: List<ExceptionDispatchInfo> edis = task.GetExceptionDispatchInfos(); if (edis.Count > 0) { edis[0].Throw(); Debug.Fail("Throw() should have thrown"); break; } else { Debug.Fail("There should be exceptions if we're Faulted."); throw task.Exception!; } } }以上程式碼就是GetResult()方法,間接呼叫的邏輯,task在執行過程中產生的異常會被Add到ExceptionDispatchInfo這麼一個物件裡面,並且它內部維護了集合,具體邏輯大概就是這樣,那這個ExceptionDispatchInfo物件什麼時候設定異常的呢?看上面的il程式碼在catch裡面呼叫了task的setException方法,該方法裡面做了add操作。就到這吧,task async await使用需要多注意,很複雜。