协程的挂起、恢复和调度的原理 (二)

目录


被 suspend 修饰符修饰的函数在编译期间会被编译器做特殊处理:CPS(续体传递风格)变换,它会改变挂起函数的函数签名。

suspend fun <T> CompletableFuture<T>.await(): T

会转变成

fun <T> CompletableFuture<T>.await(continuation: Continuation<T>): Any?

编译器对挂起函数的第一个改变就是对函数签名的改变,这种改变被称为 CPS(续体传递风格)变换。

我们可以看到,函数变换之后多了一个参数Continuation,声明如下:

interface Continuation<in T> {
   val context: CoroutineContext
   fun resumeWith(result: Result<T>)
}

Continuation 包装了协程在挂起之后应该继续执行的代码;在编译的过程中,一个完整的协程可能会有多个挂起点 (suspension point) , 挂起点把协程分割切块成一个又一个续体。在 await 函数的挂起结束以后,它会调用 continuation 参数的 resumeWith 函数,来恢复执行 await 函数后面的代码。

值得一提的是,除了会返回一个本身的返回值,还会返回一个标记,COROUTINE_SUSPENDED,返回它的挂起函数表示这个挂起函数会发生事实上的挂起操作。什么叫事实上的挂起操作呢?比如:

launch {
    val deferred = async {
        // 发起了一个网络请求
        ......
    }
    // 做了一些操作
    ......
    deferred.await()
    // 后续的一些操作
    ......
}

在 deferred.await() 这行执行的时候,如果网络请求已经取得了结果,那 await 函数会直接取得结果,而不会事实上的挂起协程。

明白了这么多概念之后,我们看看一个具体的例子:

val a = a()
val y = foo(a).await() // 挂起点 #1
b()
val z = bar(a, y).await() // 挂起点 #2
c(z)

这里有两个挂起点,编译后可以看到生成的伪字节码:

class <anonymous_for_state_machine> extends SuspendLambda<...> {
    // 状态机当前状态
    int label = 0
    
    // 协程的局部变量
    A a = null
    Y y = null
    
    void resumeWith(Object result) {
        if (label == 0) goto L0
        if (label == 1) goto L1
        if (label == 2) goto L2
        else throw IllegalStateException()
        
      L0:
        a = a()
        label = 1
         // 'this' 作为续体传递
        result = foo(a).await(this)
         // 如果 await 挂起了执行则返回
        if (result == COROUTINE_SUSPENDED) return
      L1:
        // 外部代码调用resumeWith 
        y = (Y) result
        b()
        label = 2
        result = bar(a, y).await(this)
        if (result == COROUTINE_SUSPENDED) return 
      L2:
        Z z = (Z) result
        c(z)
         // label = -1 代表已经没有其他的步骤了
        label = -1
        return
    }          
}    

在这段伪代码中,我们很容易理解它的实现逻辑:L0 代表挂起点1之前的续体,首先goto L0开始,直到调用挂起点1的 result = foo(a).await(this) 方法,this就是续体,如果 await 没挂起,直接使用结果跳入L1中;如果挂起了则直接返回,await 方法执行完后,调用 await 方法体中的 Continuation 对象,调用它的 resumeWith ,goto L1,依次类推。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值