Kotlin协程挂起恢复源码分析以及手写简单实现

1,013 阅读3分钟

协程挂起原理

先来看一个例子

结果就是先打印test3 ,然后5s过后依次打印 true-> test2 -> test1->test ,上面这个代码在kotlin看是看不出什么猫腻,因为kotlin 编译器会帮我们干很多事,我们转成java代码来看。

首先看test方法

在MainActivityonCreateonCreate1中我们看到调用test传了个this

所以r5就是MainActivityonCreateonCreate1 , MainActivityonCreateonCreate1继承自SuspendLambda ,用来启动协程,关于怎么启动协程这里就不展开了。

首先第一步会判断r5是不是com.example.test.MainActivitytesttest1 的实例,如果不是则执行L17 。com.example.test.MainActivitytesttest1是什么我们稍后再看,L17 中直接new com.example.test.MainActivitytesttest1将MainActivityonCreateonCreate1传入构造器中, 并将r0 指向它。然后接着执行L6 ,L6中就是协程状态机的代码了,刚开始 lable =0 ,会执行L14 此时会将lable =1 ,然后调用了test1 ,并把r5传入,并且r5 =r0 ,所以r5=com.example.test.MainActivitytesttest1 。

test1 ,test2 都类似我们就不看了,来看test3。

test3中开始部分还是和test类似,如果参数r11 不是com.example.test.MainActivitytest3test31则去执行L20 new com.example.test.MainActivitytest3test31然后将r0 指向它,接着执行协程状态机中的代码。

这里的状态机的代码就很有意思了,因为suspendCancellableCoroutine是内联方法,所以

suspendCancellableCoroutine中的代码经编译器处理都会在test3中。所以可以看到uCont 其实就等于 r11 = com.example.test.MainActivitytest3test31。协程状态机会先执行第一个状态0 ,并将状态 =1 ,状态0中会构建CancellableContinuationImpl 并将r11 传入其中,然后调用了Handler的post方法,最后调用CancellableContinuationImpl#getResult , getResult这里会先返回COROUTINE_SUSPENDED,接着执行L17 ,return COROUTINE_SUSPENDED 。此时test test1 test2 test3 都收到了return 的 COROUTINE_SUSPENDED , 所以都被挂起了。

总结一下: 如果每个suspend方法中会调用到其他suspend方法 ,编译器会给方法会生成类似下图的的类

这些类都是继承自ContinuationImpl , 被称为续体

以test3为例,当被恢复的时候最终会调用到invokeSuspend方法。记住此时 lable = lable | Integer.MIN_VALUE 然后再次执行test3方法 ,并且此时参数传入this 。

test3方法中 , lable&Integer.MIN_VALUE , 这个时候 1 | Integer.MIN_VALUE & Integer.MIN_VALUE 此时不等于0 ,所以不会走到L20 , 接着往下执行 1 | Integer.MIN_VALUE - Integer.MIN_VALUE = 1 , 继续执行L6 协程状态机中的此时Lable =1 所以走到L11 , L11执行完 接着执行L12 , L12就是 调用println(true),调用完之后 return Unit.INSTANCE 这个方法就结束了。

总结一下: 每个协程方法中如果调用了其他suspend 方法, 编译器就会给协程方法生成一个继承自ContinuationImpl的类,用来管理协程方法执行的状态 ,类名就是方法名后面拼接1,初始化这个类的时候会把挂起方法所在类的实例和夫续体传进去,以此例子为里MainActivity1,初始化这个类的时候会把挂起方法所在类的实例和夫续体传进去 ,以此例子为里 MainActivitytest11初始化的时候会传入MainActivityMainActivity1 初始化的时候会传入MainActivity 和 MainActivityonCreate1MainActivity1 , MainActivityonCreate1不能算是顶级协程,因为它是继承自SuspendLambdaSuspendLambda又是继承自ContinuationImpl,所以和MainActivity1不能算是顶级协程 ,因为它是继承自SuspendLambda ,SuspendLambda又是继承自ContinuationImpl , 所以和 MainActivitytest11应该是同级别的。MainActivity1应该是同级别的。 MainActivitytest1$1中传入的父协程才是顶级协程,这么就不分析了,之前文章有写过。

粗略画了一张图。

协程恢复原理

记得我们调用 it.resume("") 就可以恢复协程执行了 , it没什么神秘的就是CancellableContinuationImpl ,我们跟进看看resume ,

来张官方的图,

tryResume() 方法会把状态改为RESUMED , 之前getResult的时候调用了trySuspend把状态改为SUSPENDED 。

调用resume最终会调用到DispatchedTask.resume ,delegate就是MainActivitytest3test31

internal open class CancellableContinuationImpl<in T>(
    final override val delegate: Continuation<T>,
    resumeMode: Int
)
internal fun <T> DispatchedTask<T>.resume(delegate: Continuation<T>, undispatched: Boolean) {
    // This resume is never cancellable. The result is always delivered to delegate continuation.
    val state = takeState()
    val exception = getExceptionalResult(state)
    val result = if (exception != null) Result.failure(exception) else Result.success(getSuccessfulResult<T>(state))
    when {
        undispatched -> (delegate as DispatchedContinuation).resumeUndispatchedWith(result)
       //delegate = MainActivity$test3$1
        else -> delegate.resumeWith(result)
    }
}

所以会调用到MainActivitytest3test31的resumeWith , resumeWith之前分析过了,再贴一下,

MainActivitytest3test31#resumeWith

    public final override fun resumeWith(result: Result<Any?>) {
        var current = this
        var param = result
        while (true) {
            probeCoroutineResumed(current)
            with(current) {
                //此时completion = MainActivity$test2$1
                val completion = completion!! 
                val outcome: Result<Any?> =
                    try {
                        //执行MainActivity$test3$1 的invokeSuspend ,调用test3方法
                        val outcome = invokeSuspend(param)
                        //此时返回Unit.INSTANCE所以不会return
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    //把current = MainActivity$test2$1
                    //执行MainActivity$test2$1 
                    //param = Unit.INSTANCE
                    current = completion
                    param = outcome
                } else {
                    //是顶级协程。执行完这个协程就退出了
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }

所以这个方法会依次执行MainActivitytest3test31 -> MainActivitytest2test21 -> MainActivitytest1test11 -> MainActivitytesttest1 -> AbstractCoroutine -> 退出协程

手写简单实现

以下是基于上述分析,手写了一个简单的实现,加速大家对挂起和恢复的理解。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


        test(object : KContinuation<Any> {
            override fun resumeWith(result: Any) {
                println("main 顶级协程结束")
            }

        })

    }


    fun test(continuation: KContinuation<Any>): Any {
        var curContinuation: KContinuation<Any> = continuation
        if (curContinuation !is test_0) {
            curContinuation = test_0(this, continuation)
        }
        when (curContinuation.lable) {
            0 -> {
                curContinuation.lable = 1
                println("main test")
                val res = test1(curContinuation)
                if (res == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
            }

            1 -> {

            }

            else -> {

            }
        }
        println("main test 恢复")
        return Unit
    }


    fun test1(continuation: KContinuation<Any>): Any {
        var curContinuation: KContinuation<Any> = continuation
        if (curContinuation !is test_1) {
            curContinuation = test_1(this, continuation)
        }

        when (curContinuation.lable) {
            0 -> {
                curContinuation.lable = 1
                println("main test1")
                Handler().postDelayed({
                    curContinuation.resumeWith("")
                }, 5000)
                return COROUTINE_SUSPENDED
            }

            1 -> {

            }

            else -> {

            }
        }

        println("main test1 恢复")
        return Unit
    }
}


class test_0(val mainActivity: MainActivity, con: KContinuation<Any>) : KBaseContinuationImpl(con) {
    var lable = 0
    override fun invokeSuspend(result: Any): Any {
        return mainActivity.test(this)
    }

}

class test_1(val mainActivity: MainActivity, con: KContinuation<Any>) : KBaseContinuationImpl(con) {
    var lable = 0
    override fun invokeSuspend(result: Any): Any {
        return mainActivity.test1(this)
    }

}

interface KContinuation<T> {
    public fun resumeWith(result: T)
}

abstract class KBaseContinuationImpl(public val completion: KContinuation<Any>?) :
    KContinuation<Any> {
    override fun resumeWith(result: Any) {
        var current = this
        var param = result
        while (true) {
            with(current) {
                val completion =
                    completion!! // fail fast when trying to resume continuation without completion
                val outcome: Any = try {
                    val outcome = invokeSuspend(param)
                    if (outcome === COROUTINE_SUSPENDED) return
                    Result.success(outcome)
                } catch (exception: Throwable) {
                    Result.failure(exception)
                }
                if (completion is KBaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }

    protected abstract fun invokeSuspend(result: Any): Any

}