kotlin协程

协程是什么

协程实际上是一个轻量级的线程,可以挂起并稍后恢复

kotlin中,协程把异步编程放入库中来简化这类操作。程序逻辑在协程中顺序表述,而底层的库会将其转换为异步操作。库会将相关的用户代码打包成回调,调度其执行到不同的线程,而代码依然像顺序执行那么简单。

协程的优势

协程主要是让原来要使用“异步+回调”写出来的复杂代码,简化成看起来是同步的代码,本质上是callback的语法糖。

kotlin标准库提供的基本的元素/操作

suspend 关键字

Kotlin在1.1版本新增加了 suspend 关键字,可以用来修饰函数或者 lambda 表达式

1
2
3
suspend fun suspendFun(): String {}
val suspendLambda1: suspend () -> String = {}
val suspendLambda2 = suspend {}

被suspend修饰的方法只能在另一个suspend方法或者一个协程中被调用,suspend表示这个方法是一个可中断挂起的方法

CoroutineContext

CoroutineContext是协程的上下文,表示一系列用户自定义的Object,也就是代表代码块执行在哪个场景

1
2
3
4
5
6
interface CoroutineContext {
operator fun <E : Element> get(key: Key<E>): E?
fun <R> fold(initial: R, operation: (R, Element) -> R): R
operator fun plus(context: CoroutineContext): CoroutineContext
fun minusKey(key: Key<*>): CoroutineContext
}
  • get:获取当前指定Key对应的Context
  • fold:类似集合的fold方法,用于累加
  • plus:重载操作符plus,使得context之间可以用’+’符号运算获得一个新的context
  • minusKey:获得除指定Key对应的Context之外的所有Context

Continuation

表示协程中的执行单元

1
2
3
4
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}

ContinuationInterceptor

Continuation的拦截器,继承自CoroutineContext,如果当协程的Context包含ContinuationInterceptor那么当前协程内所有的Continuation在执行前都会经过拦截器的替换。

创建和启动协程

创建协程的方式很简单,我们需要一个suspend lambda和一个Continuation,标准库为suspend lambda定义了扩展方法startCoroutine

1
2
3
4
5
public fun <T> (suspend () -> T).startCoroutine(
completion: Continuation<T>
) {
createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}

该方法创建并启动一个协程,该协程的Context由传入的Continuation提供,且lambda执行完毕后回调该Continuation的resumeWith方法

挂起/恢复协程

通过在一个协程中调用标准库提供的suspendContinue方法来挂起当前协程

1
2
3
4
5
6
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
val safe = SafeContinuation(c.intercepted())
block(safe)
safe.getOrThrow()
}

该方法接受一个参数为Continuation的lambda,开始执行该lambda并挂起当前协程,如果想要恢复协程的执行,通过调用传入的Continuation的resumeWith方法来恢复

Kotlinx提供的协程库

1
2
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'

对基础库协程的封装,提供更加简洁的api

1
2
3
4
5
6
7
8
9
10
GlobalScope.launch(Dispatchers.Main) {
launch {
delay(1000)
Log.d("Debug", "launch 1")
}
launch {
delay(100)
Log.d("Debug", "launch 2")
}
}

原理

可挂起的方法采用CPS(Continuation-Passing-Style)实现,每一个可挂起的方法被调用的时候都会被隐式的传递一个Continuation参数来表示后续的操作。

采用Continuation则是一种函数调用方式,它不采用堆栈来保存上下文,而是把这些信息保存在continuation record中。这些continuation record和堆栈的activation record的区别在于,它不采用后入先出的线性方式,所有record被组成一棵树(或者图),从一个函数调用另一个函数就等于给当前节点生成一个子节点,然后把系统寄存器移动到这个子节点。一个函数的退出等于从当前节点退回到父节点。
最大的好处就是,它可以让你从任意一个节点跳到另一个节点。而不必遵循堆栈方式的一层一层的return方式。比如说,在当前的函数内,你只要有一个其它函数的节点信息,完全可以选择return到那个函数,而不是循规蹈矩地返回到自己的调用者。你也可以在一个函数的任何位置储存自己的上下文信息,然后,在以后某个适当的时刻,从其它的任何一个函数里面返回到自己现在的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
val a = a()
val y = foo(a).await() // suspension point #1
b()
val z = bar(a, y).await() // suspension point #2
c(z)

class <anonymous_for_state_machine> extends SuspendLambda<...> {
// The current state of the state machine
int label = 0

// local variables of the coroutine
A a = null
Y y = null

void resumeWith(Object result) {
if (label == 0) goto L0
if (label == 1) goto L1
if (label == 2) goto L2
else throw IllegalStateException()

L0:
// result is expected to be `null` at this invocation
a = a()
label = 1
result = foo(a).await(this) // 'this' is passed as a continuation
if (result == COROUTINE_SUSPENDED) return // return if await had suspended execution
L1:
// external code has resumed this coroutine passing the result of .await()
y = (Y) result
b()
label = 2
result = bar(a, y).await(this) // 'this' is passed as a continuation
if (result == COROUTINE_SUSPENDED) return // return if await had suspended execution
L2:
// external code has resumed this coroutine passing the result of .await()
Z z = (Z) result
c(z)
label = -1 // No more steps are allowed
return
}
}

ViewModel支持的协程

https://craigrussell.io/2019/03/coroutine-support-in-viewmodels-using-the-new-viewmodelscope-extension-property/

参考资料

https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md#coroutine-context
https://www.cnblogs.com/cheukyin/p/6444860.html