概念
CoroutineContext 说白了记录的是:协程运行所需要的属性,如调度器、Job 等。这些属性在协程中被叫做 Element,每一个都有唯一的 key 进行标记。
CoroutineContext 以链表结构管理所有的 element,链表的节点被记为 CombinedContext:内部通过 left 指向链表的前一个节点 —— 有可能是单纯的 element,也有可能是另一个 CombinedContext。
同时这些 Element 可以支持加、减操作,现在看一下协程内部是如何对 Element 进行管理的。
加操作
加操作是通过重写 plus() 函数进行实现的,代码如下
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext)
// 如果 context 是空的,不需要执行任何操作
this
else // fast path -- avoid lambda creation
// 否则对 element 进行累加操作
// fold 操作逻辑是:从左到右遍历 context 中所有 element
// 例如有 element 1,2,3
// 那么 fold 的 lambda 第一次操作是 1,2
// 第二次参数是:上一次 lambda 的返回值,3
// 因此 fold 会遍历到最开始的两个 element
context.fold(this) { acc, element ->
// 对 context 执行减法操作
// 如果 acc 中不含 element.key,返回的结果还是 acc
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext)
element
else {
// make sure interceptor is always last in the context (and thus is fast to get when present)
val interceptor = removed[ContinuationInterceptor]
if (interceptor == null)
// 如果没有 ContinuationInterceptor 就直接返回 CombinedContext
CombinedContext(removed, element)
else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
// 对于普通的 element,其 fold 逻辑如下:
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
举例说明整个加操作的流程,比如
e1 + e2 + e3
从左往右,先看 e1 + e2,相当于调用 e2.fold(e1),最终得到 CombinedContext(e1,e2),表达式转为 CombinedContext(e1,e2) + e3,结合 fold() 逻辑最终结果就成 CombinedContext(CombinedContext(e1,e2),e3)
在不考虑 ContinuationInterceptor 前提下,回顾整个 plus 逻辑可发现协程是以如下形式组织管理所有 element
上图中每一个方框表示一个 CombinedContext下图中每一个方框表示一个 CombinedContext,最终程序拿到的是 CombinedContext,它通过 left 依次指向前面的 context。
减操作
实际使用中并没有办法像使用 + 号一样使用 - 号,但在 plus() 方法中调用了 minusKey(),故且称之为减操作。
减操作也分两种,普通的 element 逻辑如下
// 如果 key 相同,相当于将当前 element 从 context 中删除
// 所以返回 empty
// 否则跟当前 element 无关,直接返回当前 element 即可
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
另一种是 CombinedContext 的减操作,每一个 CombinedContext 通过 left 指向它左侧的 context,通过 element 指向当前操作的 element
public override fun minusKey(key: Key<*>): CoroutineContext {
// 如果当前操作的 element 要被减去,直接返回它左侧的 ctx 即可
element[key]?.let { return left }
// 否则,递归使用左侧 context 进行减操作
val newLeft = left.minusKey(key)
return when {
// 左侧 context 中也不含有 key 对应有 element,该次操作不会产生任何效果
// 返回 this 即可
newLeft === left -> this
// 左侧 context 就是要减去的 element,返回 element 即可
newLeft === EmptyCoroutineContext -> element
// 否则说明左侧 ctx 有变化,需要重新封闭一个
else -> CombinedContext(newLeft, element)
}
}
获取
获取逻辑就很简单:在链表上递归查找没有 key 对应的 element 即可。
override fun <E : Element> get(key: Key<E>): E? {
var cur = this
while (true) {
// 如果当前就有,直接可以返回了
cur.element[key]?.let { return it }
// 否则通过 left 指向前一个节点,再次查找
val next = cur.left
if (next is CombinedContext) {
cur = next
} else {
return next[key]
}
}
}
总结
context 的整体逻辑很简单,通过 get() 的逻辑可以发现:后加的同 key 的 element 会覆盖先加的 element。