• kotlin的suspend对比csharp的async&await


    协程的出现大大降低了异步编程的复杂度,可以让我们像写同步代码一样去写异步代码,如果没有它,那么很多异步的代码都是需要靠回调函数来一层层嵌套,这个在我之前的一篇有介绍 rxjava回调地狱-kotlin协程来帮忙

    本篇文章主要介绍

    • kotlin的suspend函数在编译生成了怎样的代码
    • csharp的async&await在编译生成了怎么样的代码
    • 这两者相比较,引发怎样的思考

    kotlin的suspend函数demo

    image
    image

    这里针对kotlin的语法以及协程的具体用法细节不过多介绍,就当你已了解

    稍微注意下runBlocking函数比较特别,

    如下图:它接受了一个suspend的block函数

    image
    image

    所以我上面的demo这里面有其实有三个suspend函数!

    在idea我们可以把这个kotlin代码反编译成java代码

    image
    image

    这个反编译后的java代码 有很多报错是无法直接copy出来运行的(这就没有csharp做的好,csharp反编译出来的代码至少不会报红),

    image
    image

    看代码的确是一个状态机控制函数和一个匿名类,还原成正常的java代码如下:

    image
    image

    比如test1函数

    
    public static Object test1(Continuation continuation) {
        CoroutineTest1 continuationTest1;
        label20:
        {
            if (continuation instanceof CoroutineTest1) {
                continuationTest1 = (CoroutineTest1) continuation;
                int i = continuationTest1.label & Integer.MIN_VALUE;
                if (i != 0) {
                    continuationTest1.label -= Integer.MIN_VALUE;
                }
                break label20;
            }
            continuationTest1 = new CoroutineTest1(continuation);
        }
    
        Object result = (continuationTest1).result;
        Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        String var1;
        switch ((continuationTest1).label) {
            case 0:
                ResultKt.throwOnFailure(result);
                var1 = "test1-start";
                System.out.println(var1);
                (continuationTest1).label = 1;
                if (test2(continuationTest1) == var4) {
                    return var4;
                }
                break;
            case 1:
                ResultKt.throwOnFailure(result);
                break;
            default:
                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        }
    
        var1 = "test1-end";
        System.out.println(var1);
        return Unit.INSTANCE;
    }
    
    final static class CoroutineTest1 extends ContinuationImpl {
        Object result;
        int label;
    
        public CoroutineTest1(@Nullable Continuation<Object> completion) {
            super(completion);
        }
    
        @Nullable
        public Object invokeSuspend(@NotNull Object $result) {
            this.result = $result;
            this.label |= Integer.MIN_VALUE;
            return test1(this);
        }
    }

    其他的函数也类似,完整的代码请查看:

    https://gist.github.com/yuzd/cf67048777f0eb8fc1b3757f5bf9e8f3

    整个运行流程如下: image

    kotlin协程的挂起点是怎么控制的,异步操作执行完后它知道从哪里恢复?

    不难看出来suspend函数其实在编译后是变成了状态机,将我们顺序执行的代码,转换成了回调的形式 父suspend函数里面调用子suspend函数,其实是把自己传给了子suspend状态机,如果子函数挂起了,等子函数恢复后直接调用父函数(因为通过状态机的label来控制走不同逻辑,去恢复当时的调用堆栈)

    这就是协程的挂起与恢复机制了

    csharp的async&await

    demo

    static async Task Main(string[] args)
    {
       await test1();      
       Console.WriteLine("Let's Go!");
    }
    
    async Task test1(){
      Console.WriteLine("test1-start");
      await test2();
      Console.WriteLine("test1-end");
    
     }
    
    async Task test2()
    {
      Console.WriteLine("test2-start");
      await Task.Delay(1000);
      Console.WriteLine("test2-end");
     }

    我们反编译查看下编译器生成了怎样的状态机

    image
    image

    看反编译的代码比较吃力,我还原成了正常代码,

    static Task CreateMainAsyncStateMachine()
    {
     MainAsyncStateMachine stateMachine = new MainAsyncStateMachine
     {
      _builder = AsyncTaskMethodBuilder.Create(),
      _state = -1
     };
     stateMachine._builder.Start(ref stateMachine);
     return stateMachine._builder.Task;
    }
    
    struct MainAsyncStateMachine : IAsyncStateMachine
    {
     public int _state;
     public AsyncTaskMethodBuilder _builder;
     public TaskAwaiter _waiter;
     public void MoveNext()
     {
      int num1 = this._state;
      try
      {
       TaskAwaiter awaiter;
       int num2;
       if (num1 != 0)
       {
        awaiter = UserQuery.CreateTest1AsyncStateMachine().GetAwaiter();
        if (!awaiter.IsCompleted)
        {
         Console.WriteLine("MainAsyncStateMachine######Test1AsyncStateMachine IsCompleted:false, 注册自己到Test1Async运行结束时运行");
         this._state = num2 = 0;
         this._waiter = awaiter;
         this._builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
         return;
        }
       }
       else
       {
        Console.WriteLine("MainAsyncStateMachine######Test1AsyncStateMachine IsCompleted:true");
        awaiter = this._waiter;
        this._waiter = new TaskAwaiter();
        this._state = num2 = -1;
       }
       awaiter.GetResult();
       Console.WriteLine("MainAsyncStateMachine######Let's Go!");
      }
      catch (Exception e)
      {
       this._state = -2;
       this._builder.SetException(e);
       return;
      }
      this._state = -2;
      this._builder.SetResult();
     }
     public void SetStateMachine(IAsyncStateMachine stateMachine)
     {
      this._builder.SetStateMachine(stateMachine);
     }
    }

    完整代码请查看 https://github.com/yuzd/asyncawait_study

    可以看出来,和kotlin其实原理差不多,都是生成一个函数加一个状态机

    区别是csharp的函数就是创建一个状态机且启动它

    // 当状态机启动时会触发 状态机的MoveNext方法的调用
    stateMachine._builder.Start(ref stateMachine);
    image
    image

    整体的执行流程如下

    image
    image

    ps:最右边的是展示如果有多个await 那么就会对应这个状态机的多个状态

    这两者相比较,引发怎样的思考

    通过查看kotlin和csharp的实现方式,我发现kotlin的生成的状态机(ContinuationImpl的实现)都是有继承关系的, 比如demo中的test2继承了test1,test继承了main(通过构造函数传递的)

    然而csharp中没有这样的关系

    这也带来了两者最大的区别,kotlin的协程绑定了scope的概念,一旦scope被取消,那么scope绑定的所有的协程也都被取消。

    这点好像在csharp中没有(如果理解有误欢迎指正)

    这在实际应用中是怎么个区别呢,举个例子

    async void testAsyncA(){
        testAsyncB();
        
        // 我想取消,或者下面运行出异常了 我也无法取消testAsyncB这个任务
        
    }
    
    async void testAsyncB(){
        // do long task
    }

    在kotlin是可以的

    image
    image
    
    suspend fun test2() = coroutineScope {
        println("test2-start")
        async {
            delay(100000);
        }
        delay(1000)
        println("test2-end")
        // 或者手动取消当前coroutineScope
        this.cancel()
    }

     

     

     

  • 相关阅读:
    【Java基础】Set集合、HashSet集合、LinkedHashSet集合的概述和特点及哈希值
    The Missing Semester
    最新推荐的直链网盘榜单
    基于CGAN增强陷窄带干扰信号的通信系统测试
    PaddleSeg(1)配置文件详解
    什么品牌洗地机性价比高?四大出色的王牌机型力荐
    Java中如何处理XML数据?
    Java FileReader类具有什么功能呢?
    java中对象的比较
    Linux 常用命令
  • 原文地址:https://www.cnblogs.com/yudongdong/p/16910049.html