前言
在反编译后我们知道挂起函数的内部执行流程就是传入一个 continuation 参数的状态机,即当一个函数定义为挂起函数时,编译器在编译后,会在参数最后传入一个 Continuation类型的参数。
这个 Continuation 到底表示什么,本篇文章我们就来探讨一下。
正文
Kotlin 协程库
其实 Kotlin 的协程框架代码非常多,但是它有一个层次关系。我们会发现最下面的叫做协程基础元素,其中就有我们熟悉的 CoroutineContext 以及 Continuation,如果仔细去看里面的代码,会发现它们居然在 kotlin 标准库中;而中间层则是我们更熟悉的、经常用的一些类,比如 Job、Flow,它们是存放在 kotlinx.coroutines 库中,我们需要单独依赖;这种关系说明了一件什么事情呢?
也就是基础元素它更像是砖块,而不同的库就是利用这些基础元素封装出的,但是目前还是 Kotlin 的协程库封装的最好。最后就是因为 Kotlin 是跨平台实现,所以有特定平台的实现,比如 JVM 中使用线程池,在 JS 中使用的是 JS 线程。
可以发现这个 Continuation 是基础元素,我们来看看这个基础元素要如何使用。
实现挂起函数
在前面学习中我们知道挂起函数必须由其他挂起函数调用或者协程调用,但是我们一直没有实现一个挂起函数,那下面我们就可以使用suspendCoroutine{}高阶函数来实现一个挂起函数,代码如下:
fun main() = runBlocking {
println("start")
val result = getLengthSuspend("Kotlin")
println(result)
println("end")
}
suspend fun getLengthSuspend(text: String): Int = suspendCoroutine {
continuation ->
thread {
//模拟耗时
Thread.sleep(3000)
continuation.resume(text.length)
}
}
这里我们定义的挂起函数 getLengthSuspend 在其内部没有调用其他挂起函数,但是也不会提醒 suspend 关键字是多余的,说明它就是一个挂起函数。上面代码执行结果如下:
这里最最无法理解的就是以continuation.resume这样异步的方式传出结果以后,挂起函数就能接收到结果呢?
这里就要结合上一篇文章所说的挂起函数原理了,runBlocking 启动的协程,也可以看成一个挂起函数,然后会有一个 continuation 传递给 getLengthSuspend() 函数,而当 getLengthSuspend 中的线程执行完后调用resume 方法,又会触发 runBlocking 的挂起函数中的 invokeSuspend 方法,从而继续执行后面的操作。
所以这里没有什么神奇的地方,结合前一篇文章的 CPS 和状态机以及 continuation 传递就非常好理解,其中continuation.resume 会触发调用点函数的 continuation 的 invokeSuspend 方法进入下一个状态机状态。


