0x02. CoroutineContext的元素们

103 阅读2分钟

本章大致介绍CoroutineContext的元素们,探究下它究竟能干什么。

在此之前先看看CoroutineContext,它作为Continuation接口仅有的两个成员之一,肯定极为重要,看下它的简要结构:

public interface CoroutineContext {

    public operator fun <E : Element> get(key: Key<E>): E?

    // 用于迭代操作元素
    public fun <R> fold(initial: R, operation: (R, Element) -> R): R

    public operator fun plus(context: CoroutineContext): CoroutineContext {
        // 省略实现
    }

    public fun minusKey(key: Key<*>): CoroutineContext

    /**内部接口**/

    public interface Key<E : Element>

    public interface Element : CoroutineContext {

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

四个方法,增删查,还有一个迭代;两个内部接口,一个定义键,一个定义元素,还是比较清晰的。

之前intercepted方法查找的是ContinuationInterceptor,那么看看ContinuationInterceptor:

public interface ContinuationInterceptor : CoroutineContext.Element {
    /**
     * The key that defines *the* context interceptor.
     */
    companion object Key : CoroutineContext.Key<ContinuationInterceptor>

    public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>

    public fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        /* do nothing by default */
    }

    // 省略
}

它实现了CoroutineContext.Element,说明也是CoroutineContext的子接口的子接口;同时内部的伴生对象实现了CoroutineContext.Key接口,说明之前在intercepted方法中查找使用的键就是它。

以此类推,CoroutineContext中的元素都实现了CoroutineContext.Element接口,而键类型都实现了CoroutineContext.Key接口,且每种键类型只存在一个元素,更像一种Set数据集。

让我们继续看下之前省略的CoroutineContext的plue方法

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相加,调用CoroutineContext默认方法plus。其中flod方法是遍历,类似foreach,minusKey是删除,类似delete。大致分析后得到两个结论:1.一般得到的结果是一个CombinedContext实例,且它包含之前的Context和当前累加的Context元素两部分,这样就形成了一种类似链表的嵌套结构;2.在原Context中存在interceptor的情况下,interceptor总是存放在Context最外层,应该是为了优化读取效率。flod和minusKey方法也能在CombinedContext中看到实现,这里就不分析了。

实现了CoroutineContext.Key接口的类除了ContinuationInterceptor还有别的别的类型,如下图:

keys.PNG

甚至我们自己也可以仿照着实现一种CoroutineContext元素,以实现特殊需求,有兴趣的可以实现一下。

这里面有几种元素比较重要,如CoroutineDispatcher,CoroutineExceptionHandler,Job等,接下来会大致介绍它们的作用。