CoroutineContext的plus operation
CoroutineContext中plus()方法会用到三个方法,这三个方法以虚函数的形式定义在CoroutineContext中:
/**
* Returns the element with the given [key] from this context or `null`.
*/
public operator fun <E : Element> get(key: Key<E>): E?
/**
* Accumulates entries of this context starting with [initial] value and applying [operation]
* from left to right to current accumulator value and each element of this context.
*/
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
/**
* Returns a context containing elements from this context, but without an element with
* the specified [key].
*/
public fun minusKey(key: Key<*>): CoroutineContext
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
context.fold(this) { acc, element ->
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) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
这三个方法都是在子类中实现的,其第一代子类如下图所示:
fold展开操作
在plus()方法中先判断被加对象是否是EmptyCoroutineContext,若是则返回自己做为加法的结果,若不是则调用fold()函数进一步处理。这时被加的context对象类型可能是CombinedContext也可能是Element,先看在Element中的实现:
public interface Element : CoroutineContext {
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
}
可以看到直接调用了operation Lambda表达式,即为context.fold(this) { acc, element ->},则acc为加数,element为被加数。再看一下在CombinedContext中的实现:
internal class CombinedContext(
private val left: CoroutineContext,
private val element: Element
) : CoroutineContext, Serializable {
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(left.fold(initial, operation), element)
}
CombinedContext类中包含两个成员left和element,left的实际类型可能是CombinedContext或Element,其包含关系如下图所示。参考Kotlin协程上下文CoroutineContext是如何可相加的。
由上面代码和图可知CombinedContext的fold()函数会递归调用left的fold()方法,每一次递归都会执行一次operation操作,知道left的类型是Element为止。
minusKey减法操作
回到CoroutineContext的plus()方法,下一步会执行acc.minusKey(element.key),acc的类型也有两种情况。先看acc为Element时:
public interface Element : CoroutineContext {
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
}
这里相当于是做减法,当被减数Element中的key等于减数的key时,返回EmptyCoroutineContext,当不等时,则返回该Element对象表示不包含减数。
若acc类型的CombinedContext时:
//internal class CombinedContext
public override fun minusKey(key: Key<*>): CoroutineContext {
element[key]?.let { return left }
val newLeft = left.minusKey(key)
return when {
newLeft === left -> this
newLeft === EmptyCoroutineContext -> element
else -> CombinedContext(newLeft, element)
}
}
首先判断element是否是key对应的对象,若是则element[key]返回该对象,减去该对象后就将剩下的left返回。若不是则递归调用left的minusKey()方法继续做减法,newLeft则为做完减法的结果对象,若newLeft === left表明left不包含该key什么都没有减去,则返回该this对象,若newLeft === EmptyCoroutineContext表明left即是该key所对应对象,减去left后即应返回element,其余的话将newLeft和element组合成一个CombinedContext对象返回。
get获取操作
再回到CoroutineContext中plus()方法,removed则是做完减法后的结果,若removed === EmptyCoroutineContext表明减数和被减数相等,因此相加的结果是减数element,若不想等则继续调用removed[ContinuationInterceptor],对应Element和CombinedContext中的get()方法。先看Element中的get()方法:
//public interface Element
public override operator fun <E : Element> get(key: Key<E>): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null
很简单即判断传入的key和Element的key是否相等,从而决定是否返回this。再看一下CombinedContext中的get()方法:
//internal class CombinedContext
override fun <E : Element> get(key: Key<E>): E? {
var cur = this
while (true) {
cur.element[key]?.let { return it }
val next = cur.left
if (next is CombinedContext) {
cur = next
} else {
return next[key]
}
}
}
在while循环中,先是判断element是否是key对应的对象,若是则返回elememt对象。若不是则判断left是否是CombinedContext类型,若是则令cur = next进入下一次循环,这里实际是递归的While形式。若left不是CombinedContext类型,则调用其get()方法,参考Element的get()方法。
回到CoroutineContext中plus()方法,removed[ContinuationInterceptor]是检查removed中是否包含ContinuationInterceptor对象,若不包含则通过CombinedContext(removed, element)将removed和element组合成一个CombinedContext对象返回。若包含则将该ContinuationInterceptor对象从removed中移除产生新的left,然后再将该ContinuationInterceptor对象放在最外层element的位置,组合成一个CombinedContext对象返回。这样做是通过plus操作保持ContinuationInterceptor在最外层element的位置,当获取ContinuationInterceptor时十分快捷,无需递归查找。