Kotlin 协程
本文只是浅析 Kotlin 协程在各平台的实现, 以及跨平台兼容方案。 如果要看Kotlin协程api的用法,请移步我的另一篇文章
言归正传, 我们聊聊"协程".
如果有讲的不好的地方, 欢迎在下方评论
协程介绍
首先, 什么是协程?
有人说协程拥有自己的寄存器上下文和栈的轻量级执行单元, 可以在控制执行上下文的切换。
熟悉Javascript生成器的人可能会说,协程就是回调的语法糖,本质�根本不关方法栈上下文切换啥事。
到底谁说的是对的?
别着急, 看看维基百科是怎么说的:
我们先看看下面这段描述:
Coroutines are computer-program components that generalize subroutines for non-preemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations
协程就是可以生成非抢占式子程序的计算机程序, 可以允许程序有多个特定的地方可以挂起或者恢复执行。
Kotlin 协程在各平台编译成什么?
熟悉Javascript生成器的人可能会说, 协程就是回调的语法糖, 本质�根本不关方法栈上下文切换啥事。
在Kotlin 协程之前, 我们讲讲关于Js的故事。
大家都知道Js有诸多版本, 我们经常用着Es6、7、8的语法, 使用Babel将它们编译成低版本, 再在浏览器或者低版本node执行。 这里我们单纯对Node.js进行讨论。
低版本的Node有协程吗? 你可以说没有。实现 coroutine 的方式有很多,比如 ES6 的 generator,ES8(
你没有看错, 不是ES7
) 的 async/await。在低版本的Node中, 自然是没有协程的, 高级的生成器函数将会编译成普通的Js回调、状态机代码(我们后面会讲到
), 但是在Node.js 8之后的版本, node.js原生支持了协程, 实现了寄存器上下文和栈的切换。
所以, 我们是不是可以说一段高版本Js代码如果经过编译成低版本代码后, 就不在拥有协程了呢?
这么说是不公平的, 如果这么说的话, 那本文就该换标题了, Kotlin就没有协程了
上层不应该关心下层的具体实现,而只应该关心下层提供的接口
大家都知道, Jvm也是没有原生协程的,大部分平台, 包括Kotlin-js编译后的es5 js代码, 也并没有关于协程的原生实现, (或者说没有用到)。 那么把协程看做重要特性的Kotlin, 编译后是怎么样的呢?
Talk is cheap, show me your code.
让我们看看下面这段(js 和 kotlin 混写的)伪代码
suspend function postItems(item) {
let tk = getToken(item)
let post = doPost(tk)
return post
}
interface StateMachine {
status: number,
item: Any,
continuation: Continuation
}
function postI(item, sm: Continuation) {
if (!sm instanceof StateMachine) {
sm = {
status: 0,
item: null,
continuation: sm
}
}
switch sm.status:
case 1:
sm.status += 1;
getToken(item, (tk) => {
sm.item = tk;
postI(null, sm)
})
case 2:
sm.status += 1;
doPost(sm.item, (post) => {
sm.item = post;
postI(null, sm)
})
case 3:
sm.continuation.resume(sm.item)
return
}
简析:
postItems 是一个kotlin的suspend方法, 他先通过http请求拿到token, 再通过token发起一个http post 请求, 将返回的对象 返回给调用者。
对应编译后的实现就成了postI,一个普通的方法,接受一个Continuation类型对象的方法。而这个 Continuation,可以近似看做一个回调函数, 也就是说, 在jvm这边, 你的suspend function, 最终变成了接受一个回调函数为参数的普通方法, 而且在其它平台也大抵类似。
那么suspend function里面的suspend function调用是怎么调的呢?看上面的伪代码,你大概可以知道: 通过回调, 在getToken的回调里面继续调用postItems方法,但是状态已经改变,则可以执行到下一个suspend挂起点,以此类推,通过这个状态机实现了原来kotlin suspend 方法的
挂起与恢复
。
Kotlin 跨平台实现
虽然Kotlin 编译后的普通方法提供了回调的应用, 但是正如官方文档中所说, 你永远不要尝试在各个平台实现这些回调, 除非你是个 "coroutine master"
, 实现context等都是有坑的。
Kotlin 协程 node.js
kotlin.coroutines 有个专门的 promise 库, 所以如果你要暴露一个api给外面的js库去调用, 你可以这么写:
suspend fun helloWorld() {
delay(1000)
return "1212"
}
fun theApiYouWantToExposeAndUsedInOtherJsModules() {
Globalscope.promise {
helloworld()
}
}
Kotlin 协程 jvm
Jvm 有runblocking 的api阻塞当前线程执行, 这个是一个优点,因为在node.js单线程里面你没办法runblocking.
所以你可以暴露这样的api:
suspend fun helloWorld() {
delay(1000)
return "1212"
}
fun theApiYouWantToExposeAndUsedInOtherJvmProgram() = runBlocking {
helloworld()
}
}
当然为了性能更推荐的方式是利用jvm 独有future api 返回一个Future 对象:
fun theApiYouWantToExposeAndUsedInOtherJvmProgram() = future {
helloworld()
}
}
Kotlin 协程 native
native 也有runblocking, 不过好像在swift 里面有个天坑。我对swift不是很了解, 解决方案给你扔这了: https://github.com/ktorio/ktor/issues/678
如果有问题或者谬误, 欢迎在评论指出。
网友评论