coroutine的context有趣的实现方式

context是一特定场景下的执行环境,我们会在context中配置资源和其它对象。针对浮动框场景下,举个例子:

1
2
3
4
5
6
class FloatPanelContext {
private Context mContext;
private View mRootView;
// ...
// get set
}

但是kotlin的coroutine的context的实现,有点独特

CoroutineContext

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public interface 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 and elements from other [context].
* The elements from this context with the same key as in the other one are dropped.
*/
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)
}
}
}

/**
* Returns a context containing elements from this context, but without an element with
* the specified [key].
*/
public fun minusKey(key: Key<*>): CoroutineContext

/**
* Key for the elements of [CoroutineContext]. [E] is a type of element with this key.
*/
public interface Key<E : Element>

/**
* An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself.
*/
public interface Element : CoroutineContext {
/**
* A key of this coroutine context element.
*/
public val key: Key<*>

public override operator fun <E : Element> get(key: Key<E>): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null

public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)

public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
}
}

CoroutineContext是一个interface,里边有个嵌套的interface是Element,Element是继承自CoroutineContext,Element中有一个对应的Key。每个操作对象都是直接或间接地实现了Element,比如说Job,CoroutineDispatcher,ContinuationInterceptor。

CoroutineContext定义了两个操作符重载函数,get和plus,get是操作符[],plus是操作符+,还有个minusKey。要理解这些函数的意义,得先搞明白协程上下文的表示方法。

链表

协程上下文其实是一个左向链表,至于链表,举个例子,通常是这样的

1
data class Node(var next: Node)

但是这个左向链表,是这样的

1
data class Node(var left: Node)

从结构上看没啥改变,只不过属性名不同。但是呢,它的意义不同,对于普通的链表,链表引用指向头,对于左向链表,链表引用指向尾。

CombinedContext

这个表示协程上下文的链表中,每个节点是CombinedContext,它的定义主要如下:

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
42
43
44
internal class CombinedContext(
private val left: CoroutineContext,
private val element: Element
) : CoroutineContext, Serializable {

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]
}
}
}

public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(left.fold(initial, operation), element)

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)
}
}

private fun size(): Int {
var cur = this
var size = 2
while (true) {
cur = cur.left as? CombinedContext ?: return size
size++
}
}

// ...
// ...

}

在这个重载了操作符[]的get方法中,遍历链表,先在当前节点的中按照Key去寻找Element,若未找到,去从left节点中寻找。

fold方法的作用是,把自己集成到参数initial上。

minusKey方法的作用是,把这个链表上与参数key相同的Element去掉。如果是某一个节点的element的key相同,就把这个节点去掉。

size既是链表的长度。

回到CoroutineContext

现在回到CoroutineContext中,再看它的重载了+操作符的plus方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
}
}
}

把参数context加到本链表中,用的是fold方法,操作方式是那一个lambda表达式,那个lambda表达式主要做了一件事是从链表中取出来Key是ContinuationInterceptor的节点,并放到链表的尾部,因为它是左向链表,放到尾部可以直接取出来用。为什么放到尾部,还不知道,也可能是约定好的。

总结

  1. 协程的上下文并不是通常定义的一个类,而是具有链表结构的列表,每一个节点是Element,Element继承自CoroutineContext;
  2. CoroutineContext重载了+[]+可以把新的Element加到链表上,[]的参数是Key,这又可以使得整个链表具有了map的属性;
  3. CombinedContext是上下文链表的节点,它重写了一些方法,这些方法中的逻辑实现,采用了遍历和递归的方式,遍历是对链表的遍历,递归是通过调用操作符[]进行递归查找。