Kotlin 协程实现原理

从一个例子开始

命令模式 (Direct Style)

fun main() {
    postItem(Item("item"))
}

fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
}

fun requestToken(): String {
    print("Start request token ...")
    Thread.sleep(1000) // 假设网络请求很耗时
    println("... finish request token")
    return "token"
}

fun createPost(token: String, item: Item): String {
    print("Start create Post ... $token, $item")
    Thread.sleep(500) // 假设网络请求很耗时
    println(" ... finish create Post")
    return "ResponsePost"
}

fun processPost(post: String) {
    println("process post, post=$post")
}

data class Item(val i: String = "item")
Start request token ...... finish request token
Start create Post ... token, Item(i=item) ... finish create Post
process post, post=ResponsePost

命令模式编程的特点

  • 优点: 顺序, 简单, 直接
  • 缺点: 阻塞

回调模式 (Callback Style)

fun main() {
    postItem(Item("item"))
}

fun postItem(item: Item) {
    requestToken(object : Callback<String> {

        override fun onResult(token: String) {

            createPost(token, item, object : Callback<String> {

                override fun onResult(post: String) {

                    processPost(post)
                }
            })
        }
    })
}

fun requestToken(callback: Callback<String>) {
    print("Start request token ...")
    Thread.sleep(1000)
    println("... finish request token")
    callback.onResult("token")
}

fun createPost(token: String, item: Item, callback: Callback<String>) {
    print("Start create Post ... $token, $item")
    Thread.sleep(500)
    println(" ... finish create Post")
    callback.onResult("ResponsePost")
}

fun processPost(post: String) {
    println("process post, post=$post")
}

interface Callback<T> {
    fun onResult(value: T)
    fun onError(exception: Exception) {}
}

data class Item(val i: String = "item")
Start request token ...... finish request token
Start create Post ... token, Item(i=item) ... finish create Post
process post, post=ResponsePost

回调模式编程的特点

  • 优点: 异步, 非阻塞
  • 缺点: 回调嵌套, 代码难懂

连续传递模式 (CPS: Continuation-Passing Style)

  • CPS == 回调模式
  • Continuation 其实是一个通用的(generic) Callback. 通俗的讲, 一个函数的 Continuation 就是指这个函数执行完后, 需要继续执行的所有代码.

例如 requestToken() 的 Continuation 是:

val post = createPost(token, item)
processPost(post)

所以写成 CPS 的模式是:

fun postItem(item: Item) {
    requestToken { token ->
        createPost(token, item)
        processPost(post)
    }
}

同理, createPost 的 Continuation 是:

processPost(post)

全部转换为 CPS 的模式, 最终的结果为:

fun postItem(item: Item) {
    requestToken { token ->
        createPost(token, item) { post ->
            processPost(post)
        }
    }
}

协程命令模式 (Coroutines Direct Style)


fun main() {
    GlobalScope.launch {
        postItem(Item("item"))
        println("done")
    }

    println("in main")
    Thread.sleep(2000)
}

suspend fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
}

suspend fun requestToken(): String {
    print("Start request token ...")
    Thread.sleep(1000)
    println("... finish request token")
    return "token"
}

suspend fun createPost(token: String, item: Item): String {
    print("Start create Post ... $token, $item")
    Thread.sleep(500)
    println(" ... finish create Post")
    return "ResponsePost"
}

fun processPost(post: String) {
    println("process post, post=$post")
}

data class Item(val i: String = "item")

输出:

in main
Start request token ...... finish request token
Start create Post ... token, Item(i=item) ... finish create Post
process post, post=ResponsePost
done

协程本质上是把命令模式的代码转为 CPS 模式的实现

suspend fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
}

协程命令模式的代码与传统命令模式的代码非常相似, 除了加入在函数前加入 suspend 修饰符

解密挂起

挂起函数: 带有 suspend 修饰符的函数. 只能在协程中被调用. Kotlin 编译器会把挂起函数转换为 CPS 模式的函数, 比如如下挂起函数:

suspend createPost(token, item): Post

其实会编译器转换为如下的 Java 函数:

Object createPost(Token token, Item item, Continuation<Post> con)

而 Continuation 只是一个通用的回调接口, 与上面的 Callback 接口几乎一样:

Continuation<in T> {
    val context: CoroutineContext
    fun resumeWith(value: T)
}

挂起函数转换为普通函数后会多加一个 Continuation 接口参数, 函数执行完成后回调 Continuation.

为了提高性能, 减少对象分配次数, 把多个回调的实现合并为一个, 即状态机对象的Continuation:

fun postItem(item: Item, cont: Continuation) {

   // 区分上层传过来的 Continuation 与我们自己的 Continuation
  val sm = cont as? ThisSM ?: object : ThisSM {
      fun resumeWith(...) {
          // 回调回来还是调用 postItem() 方法, 并且传入this, 重用回调对象, 并且可以保存状态机的状态.
          postItem(null, this)
      }
  }

  switch (sm.label) {
      case 0:
          sm.item = item
          sm.label = 1 // label 表示下一步的标记, 而不是当前步!
          requestToken(sm)
          break;
      case 1:
          val item = sm.item
          val token = sm.result as String;
          sm.label = 2
          createPost(token, item, sm)
          break;
      case 2:
          val post = sm.result as String;
          processPost(post)
          sm.cont.resumeWith(null)  // 最后一步执行完了, 要回调上层的 Continuation
          break;
  }
}

挂起 的意思可以理解为异步执行未完成, 回调还没有回来. 理解 挂起 的含义是理解协程的关键!

猜你喜欢

转载自blog.csdn.net/cpcpcp123/article/details/112425740