Android协程(Coroutines)系列-深入理解CoroutineContext上下文

1,939 阅读2分钟

小知识,大挑战!本文正在参与“   程序员必备小知识   ”创作活动

本文同时参与 「掘力星计划」   ,赢取创作大礼包,挑战创作激励金

📚 如果您是 Android 平台上协程的初学者,请查阅上一篇文章: Android协程(Coroutines)系列-入门

❓Android协程哪里使用了上下文?

其实Android协程(Coroutines)系列-入门文章中的GlobalScope.launch中的构造方法中需要传入上下文,如果不传,默认是EmptyCoroutineContext,源码如下:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

作用域和上下文的区别只在于使用目的的不同

  • 作用域用于管理协程;
  • 而上下文只是一个记录协程运行环境的集合

🍈 CoroutineContext

协程的上下文,它包含用户定义的一些数据集合,这些数据与协程密切相关。它类似于map集合,可以通过key来获取不同类型的数据。同时CoroutineContext的灵活性很强,如果其需要改变只需使用当前的CoroutineContext来创建一个新的CoroutineContext即可。

来看下CoroutineContext的定义

@SinceKotlin("1.3")
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都有它唯一的一个Key其中的类型是Element,我们可以通过对应的Key来获取对应的具体对象。通过例子来了解

fun main() {
    var context = Job() + Dispatchers.IO + CoroutineName("名字")

    println("$context")
    println("${context[CoroutineName]}")

    context = context.minusKey(Job)

    println("minusKey:$context")
}

输出

[JobImpl{Active}@6193b845, CoroutineName(名字), Dispatchers.IO]
CoroutineName(名字)

minusKey:[CoroutineName(名字), Dispatchers.IO]

JobDispatchersCoroutineName都实现了Element接口。

如果需要结合不同的CoroutineContext可以直接通过+拼接,本质就是使用了plus方法。

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

plus的实现逻辑是将两个拼接的CoroutineContext封装到CombinedContext中组成一个拼接链,同时每次都将ContinuationInterceptor添加到拼接链的最尾部.

翻译

/**
 * 合并两个上下文,如果有重复的,用右侧的替换,其次,确保拦截器在最后的位置
 * */
 
public operator fun plus(context: CoroutineContext): CoroutineContext =
 
        if (context === EmptyCoroutineContext) {
 
            // fast path -- avoid lambda creation
 
            //如果要合并的是一个空上下文,直接返回当前的上下文
 
            this
 
        } else {
 
            //如果左右两个上下文都是有内容的
 
            context.fold(this) { acc, element ->
 
                //取出右侧的上下文的key,acc.minusKey计算出左侧上下文除去这个key后剩下的上下文内容
 
                val removed = acc.minusKey(element.key)
 
                if (removed === EmptyCoroutineContext) {
 
                    //如果左侧剩下的是空上下文,说明左侧也是只有这一个key对应的值
 
                    //所以,直接右侧替换左侧,,,返回右侧element即可
 
                    element
 
                } else {
 
                    //如果左侧的上下文有自己的内容,接下来就是要合并两个上下文了,
 
                    // 同时将相同key值的替换为右侧的
 
                    // make sure interceptor is always last in the context (and thus is fast to get when present)
 
                    // 确保拦截器始终位于上下文中的最后一个(因此在出现时可以快速获取)
 
                    //感觉这里得context 已经开始都是一个CombinedContext类型了吧
 
                    val interceptor = removed[ContinuationInterceptor]
 
                    if (interceptor == null) {
 
                        //如果左侧没有拦截器,直接将左右合并
 
                        CombinedContext(removed, element)//A
 
                    } else {
 
                        //如果左侧有拦截器,则先取出其他的上下文
 
                        val left = removed.minusKey(ContinuationInterceptor)
 
                        if (left === EmptyCoroutineContext) {
 
                            //如果其他的是空上下文,,则说明左侧只剩下一个拦截器,直接将拦截器合并到右侧的
 
                            CombinedContext(element, interceptor)//B:  注意参数位置,和A处对比
 
                        } else {
 
                            //如果左侧除了拦截器,还有其他的上下文,为了确保拦截器在最后,应该将其他的和右侧合并,
 
                            //最后再合并拦截器到右侧
 
                            CombinedContext(CombinedContext(left, element), interceptor)
 
                        }
 
                    }
 
                }
 
            }
 
        }

那么CombinedContext又是什么呢?

@SinceKotlin("1.3")
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]
           }
       }
   }
   ```
}

那么这个Key到底是什么呢?我们来看下CoroutineName

public data class CoroutineName(
    /**
     * User-defined coroutine name.
     */
    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
    /**
     * Key for [CoroutineName] instance in the coroutine context.
     */
    public companion object Key : CoroutineContext.Key<CoroutineName>

    /**
     * Returns a string representation of the object.
     */
    override fun toString(): String = "CoroutineName($name)"
}

很简单它的Key就是CoroutineContext.Key<CoroutineName>,当然这样还不够,需要继续结合对于的operator get方法,所以我们再来看下Elementget方法

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

所以我们就可以直接通过类似于Map的方式来获取整个协程中CoroutineContext集合中对应KeyCoroutineContext实例。