写点什么

Kotlin 协程实现原理概述

用户头像
Android架构
关注
发布于: 5 小时前

println(result)


}


我们来将代码 SRP 一下(单一职责):


// 加法


fun sum(a: Int,b: Int) = a + b


// x 乘以 2


fun double(x: Int) = x shl 1


// x 加 2


fun add2(x: Int) = x + 2


// 最终的 test


fun test(a: Int, b: Int) {


// 从内层依次调用,最终打印


println(add2(double(sum(a,b))))


}


可以看到,我们将原来一坨的方法,抽离成了好几个方法,每个方法干一件事,虽然提高了可读性和可维护性,但是代码复杂了,我们来让它更复杂一点。


上述代码是 让内层方法的返回值 作为参数 传递给外层方法,现在我们 把外层方法作为接口回调 传递给 内层方法:


// 加法,next 是加法做完的回调,会传入相加的结果


fun sum(a: Int, b: Int, next: (Int) -> Unit) = a + b


// x 乘以 2


fun double(x: Int, next: (Int) -> Unit) = x shl 1


// x 加 2


fun add2(x: Int, next: (Int) -> Unit) = x + 2


// 最终的 test


fun test2(a: Int, b: Int) {


// 执行加法


sum(a, b) { sum ->


// 加完执行乘法


double(sum) { double ->


// 乘完就加 2


add2(double) { result ->


// 最后打印


println(result)


}


}


}


}


这就是 CPS 的代码风格:通过接口回调的方式来实现的


假设: 我们上述的几个方法: sum()/double()/add2()都是挂起函数,那么最终也会编译为 CPS 风格的回调函数方式,也就是:原来看起来同步的代码,经过编译器的"修改",变成了异步的方法,也就是:CPS 化了,这就是 kotlin 协程的顶层实现逻辑。


现在,让我们来验证一下,我们定义一个 suspend 函数,反编译看下是否真的 CPS 化了。


// 定义挂起函数


suspend fun test(id: String): String = "hell


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


o"


反编译结果如下:


// 参数添加了一个 Continuation 参数


public final Object test(@NotNull String id, @NotNull Continuation $completion) {


return "hello";


}


可以看到,多了个 Continuation 参数,这是个接口,是在本次函数执行完毕后执行的回调,内容如下:


public interface Continuation<in T> {


// 保存上下文(比如变量状态)


public val context: CoroutineContext


// 方法执行结束的回调,参数是个范型,用来传递方法执行的结果


public fun resumeWith(result: Result<T>)


}


好,现在我们知道了 suspend 函数 是通过添加 Continuation 来实现的,我们来看个具体的业务:


// 根据 id 获取 token


suspend fun getToken(id: String): String = "token"


// 根据 token 获取 info


suspend fun getInfo(token: String): String = "info"


// 测试


suspend fun test() {


// 先获取 token,这是耗时请求


val token = getToken("123")


// 再根据 token 获取 info,这也是个耗时请求


val info = getInfo(token)


// 打印


println(info)


}


上述的业务代码很简单,但是前两步都是耗时操作,线程会卡在那里 wait 吗?显然不会,既然是 suspend 函数,那么就可以 CPS 化,等价的 CPS 代码如下:


// 跟上述相同,传递了 Continuation 回调


fun getToken(id: String, callback: Continuation<String>): String = "token"


// 跟上述相同,传递了 Continuation 回调


fun getInfo(token: String, callback: Continuation<String>): String = "info"


// 测试(只写了主线代码)


fun test() {


// 先获取 token,传入回调


getToken("123", object : Continuation<String> {


override fun resumeWith(result: Result<String>) {


// 用 token 获取 info,传入回调


val token = result.getOrNull()


getInfo(token!!, object : Continuation<String> {


override fun resumeWith(result: Result<String>) {


// 打印结果


val info = result.getOrNull()


println(info)


}


})


}


})


}


上述就是无 suspend 的 CPS 风格代码,通过传入接口回调来实现协程的同步代码风格。


接下来我们来反编译 suspend 风格代码,看下它里面是怎么调度的。

协程的底层实现-状态机

我们先来简单修改下 suspend test 函数:


// 没变化


suspend fun getToken(id: String): String = "token"


// 没变化


suspend fun getInfo(token: String): String = "info"


// 添加了局部变量 a,看下 suspend 怎么保存 a 这个变量


suspend fun test() {


val token = getToken("123") // 挂起点 1


var a = 10 // 这里是 10


val info = getInfo(token) // 挂起点 2,需要将前面的数据保存(比如 a),在挂起点之后恢复


println(info)


println(a


}


每个 suspend 函数调用点,都会生成一个挂起点,在挂起点我们要保存当前的运行状态,比如局部变量等。


反编译后的代码大致如下:


public final Object getToken(String id, Continuation completion) {


return "token";


}


public final Object getInfo(String token, Continuation completion) {


return "info";


}


// 重点函数(伪代码)


public final Object test(Continuation<String>: continuation) {


Continuation cont = new ContinuationImpl(continuation) {


int label; // 保存状态

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Kotlin协程实现原理概述