task async await 异常处理

今天跟前端对接支付接口碰到一个问题比较有代表性的问题,接口throw异常,调用链路的所有服务都没有catch,但是返回到前端的结果还是成功的,这里跟踪的麻烦点就是调用链路比较深,中间调用了5个左右的服务,跟踪来跟踪去还是没发现问题,最后要同事确认下,最后一个接口有没有给我抛出异常,同事很肯定的说返回了异常出来,最后发现倒数第二个接口确实存在问题,
 
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(...);
        }
 
以上代码不是原始代码,但是跟这份代码结构类似,我们看下这两个方法,都是async方法,看似跟普通同步方法没啥区别,其实底层执行逻辑区别很大,接着以前il代码的分析,被async修饰的方法会被编译器编译成class,并且实现状态机接口,最后我们的test方法里面的内容在状态机movenext方法里面被调用,看下il代码
 
.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
 
大概长上面那样,发现问题没?test1方法里面的throw被编译器暂时消化掉了,编译器编译时会默认加上try catch,那这个怎么处理,我task里面的异常怎么拿到?两种方案,要么await表达式,要么调用同步方法wait(),正确操作应该是
 
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(...);
        }
 
那么这有啥本质区别呢?首先编译器会把await表达式前后定义两种状态逻辑,后面的逻辑会被阻塞住(这里的阻塞可能是线程),并且hook表达式await的结果result,当hook到任务task已完成,jit会调用getResult()方法获取结果,这里有个细节需要注意了,如果task在执行过程中有异常,这时候它才会跑出来,我们可以简单看下GetResult()方法的实现。
 
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使用需要多注意,很复杂。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章