在协程第一篇中曾用不同语言实现了生产者消费者demo,在介绍了kotlin的基础api后,今天用kotlin的continuation来实现一个相同的demo。
接口定义
首先,考虑协程存在yield和resume操作,而kotlin协程没有对应的接口,需要进行封装,故resume和yield是函数是必须要的,其中yield作为生产者角色,接收一个参数表示生产的产品,resume作为消费者角色,存在一个与yield参数类型相同的返回值.。且由于他们都要等待对方完成,所以必须是suspend函数,定义如下,其中resume添加参数p,是为了能让其与yield互相通信。
suspend fun yield(p: T): T
suspend fun resume(p: T): T
其次,考虑lua协程需要手动调用create创建协程,因此我们也需要封装一个createCoroutine来创建协程,它接受一个suspend函数作为协程体,其中complete参数作为协程执行完成后返回的值,由调用者决定(主要为了统一接口)。
fun createCoroutine(complete: T, func: (suspend () -> Unit))
然后,作为消费者的一端必然需要知道当前协程是否执行结束,所以再定义isComplete函数实现判断逻辑:
fun isComplete(): Boolean
状态转换
接口定义完成后就该考虑如何实现这些接口了,从isComplete接口考虑,其内必然有存储状态的变量,接着再观察其他接口,基本可以得出应该存在四种状态:
- INIT:协程刚创建还未运行时
- RESUME:协程正在运行
- YIELD:协程被挂起了
- COMPLETE:协程执行完成
其状态流转如下图:
其中首次运行,状态从INIT流转到RESUME,接着状态在RESUME和YIELD之间不断轮转,当协程执行完毕时,状态从RESUME流转到COMPLETE。
实现
以下是完整的代码实现:
class DemoCoroutine<T>() {
companion object {
private const val STATE_INIT = 0
private const val STATE_YIELD = 1
private const val STATE_RESUME = 2
private const val STATE_COMPLETE = 3
}
private var continuation: Continuation<T>? = null
// 运行的主协程
private var coroutine: Continuation<Unit>? = null
private var state = STATE_INIT
suspend fun yield(p: T): T = suspendCoroutine {
val currState = state
val currContinuation = continuation
state = STATE_YIELD
continuation = it
if (currState == STATE_RESUME) {
currContinuation?.resume(p)
}
}
fun isComplete(): Boolean {
return state == STATE_COMPLETE
}
suspend fun resume(p: T): T = suspendCoroutine {
val currState = state
val currContinuation = continuation
state = STATE_RESUME
continuation = it
if (currState == STATE_INIT) {
coroutine?.resume(Unit)
} else if (currState == STATE_YIELD) {
currContinuation?.resume(p)
}
}
fun createCoroutine(complete: T, func: (suspend () -> Unit)) {
coroutine = suspend {
func()
}.createCoroutine(object : Continuation<Unit> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
override fun resumeWith(result: Result<Unit>) {
state = STATE_COMPLETE
continuation?.resume(complete)
}
})
}
}
demo
接下去利用此类实现一个生产者-消费者demo:
suspend fun main() {
val complete = -1
val coroutine = DemoCoroutine<Int>()
coroutine.createCoroutine(complete) {
for (i in 0 until 4) {
println("generate $i")
coroutine.yield(i)
}
}
while (!coroutine.isComplete()) {
val r = coroutine.resume(0)
if (r != complete) {
println("receive $r")
}
}
}
执行结果如下:
generate 0
receive 0
generate 1
receive 1
generate 2
receive 2
generate 3
receive 3
Process finished with exit code 0