kotlin协程之continuation实战

1,524 阅读2分钟

在协程第一篇中曾用不同语言实现了生产者消费者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接口考虑,其内必然有存储状态的变量,接着再观察其他接口,基本可以得出应该存在四种状态:

  1. INIT:协程刚创建还未运行时
  2. RESUME:协程正在运行
  3. YIELD:协程被挂起了
  4. COMPLETE:协程执行完成

其状态流转如下图:

image.png

其中首次运行,状态从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