从一个例子开始
命令模式 (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
命令模式编程的特点
- 优点: 顺序, 简单, 直接
- 缺点: 阻塞
fun main() {
postItem(Item("item"))
}
fun postItem(item: Item) {
requestToken(object : Callback {
override fun onResult(token: String) {
createPost(token, item, object : Callback {
override fun onResult(post: String) {
processPost(post)
}
})
}
})
}
fun requestToken(callback: Callback) {
print("Start request token ...")
Thread.sleep(1000)
println("... finish request token")
callback.onResult("token")
}
fun createPost(token: String, item: Item, callback: Callback) {
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 {
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 其实是一个通用的(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 con)
而 Continuation
只是一个通用的回调接口, 与上面的 Callback
接口几乎一样:
Continuation {
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;
}
}
挂起 的意思可以理解为异步执行未完成, 回调还没有回来. 理解 挂起 的含义是理解协程的关键!