CoroutineContext的作用

首要

先说总结,CoroutineContext的作用是为协程存储各品种信息,实质上是一种调集(能够理解为一种Map);存储的这些类都是CoroutineContext的具体子类,他们都有各自的作用,在运用时直接经过CoroutineContext的get办法取出,非常便利;

CoroutineContext的源码在CoroutineContext.kt文件中,代码很短,可是细节需要特别注意。先看类阐明:

Persistent context for the coroutine. It is an indexed set of Element instances. An indexed set is a mix between a set and a map. Every element in this set has a unique Key.

翻译下:
为协程供给上下文。它是Element实例的索引集。索引集是集和映射之间的混合体。这个调集中的每个元素都有一个仅有的Key。

在此主张咱们先阅览CoroutineContext源码,再看下文章 Kotlin协程源码剖析-7 Context左向链表 ;

先将文章中的示例代码拿出来,便利咱们后续检查

public class My3CoroutineName(
    val name: String
) : AbstractCoroutineContextElement(My3CoroutineName) {
    public companion object Key : CoroutineContext.Key<My3CoroutineName>
    override fun toString(): String = "CoroutineName($name)"
}
public class My4CoroutineName(
    val name: String
) : AbstractCoroutineContextElement(My4CoroutineName) {
    public companion object Key : CoroutineContext.Key<My4CoroutineName>
    override fun toString(): String = "CoroutineName($name)"
}

这段代码中有一个特别需要阐明的点就是My3CoroutineName承继了类AbstractCoroutineContextElement,该类定义如下

public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element

构造函数传递的参数类型是key类型,而咱们传递的是My3CoroutineName,形似错误但却能正常运转,因为这里用到了Kotlin的语法糖;此处的My3CoroutineName实践指向的是该类的伴生对象Key;这个用法在协程中运用广泛。

  • CoroutineScope.launch创立newContext时,combined[ContinuationInterceptor]
@ExperimentalCoroutinesApi
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    val combined = foldCopies(coroutineContext, context, true)
    val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
    return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
        debug + Dispatchers.Default else debug
}
  • AbstractCoroutine初始化时获取父Job时,parentContext[Job]
init {
    if (initParentJob) initParentJob(parentContext[Job])
}

其次

文章Kotlin协程源码剖析-7 Context左向链表 中的给出的示例对于咱们理解左向链表有着非常大的帮助,在此额外弥补个示例,小伙伴能够自己先考虑下,再比对下成果是否与自己想的共同;

fun studyContext4() {
    val my3CoroutineName = My3CoroutineName("item3")
    val my4CoroutineName = My4CoroutineName("item4")
    val my5CoroutineName = My3CoroutineName("item5")
    val newElement = (my3CoroutineName + my4CoroutineName) + my5CoroutineName
    println("(3+4)+5:$newElement")
}

输出如下

(3+4)+5:[CoroutineName(item4), CoroutineName(item5)]

咱们发现item3被移除掉了,实质原因在文章开头说过了,这个调集中的每个元素都有一个仅有的Keyitem3item5虽然是2个对象,可是他们具有相同的Key,所以在履行plus操作时,原先的item3会被移除掉,成果就是item4item5

最后

运用一个实践工作的例子来结束这篇文章。如果之前没有用过反常处理器的小伙伴,主张阅览Kotlin 协程的反常处理, Kotlin协程核心库剖析-5 Job反常处理器注意点

实践开发工作中,咱们有时需要对协程或许出现的反常增加try catch处理逻辑,可是每次编写这样的样板代码,浪费时间且毫无营养,于是就想着做个包装办法处理这段逻辑;

fun <T> CoroutineScope.launchWithCatch(
    context: CoroutineContext = EmptyCoroutineContext,
    onComplete: ((Result<T>) -> Unit)? = null,
    onCancel: (() -> Unit)? = null,
    errorHandler: ((exception: Throwable) -> Unit)? = null,
    onFinally:((Result<T>) -> Unit)? = null,
    block: suspend CoroutineScope.() -> T
): Job {
    val ref = AtomicReference<CoroutineContext>(null)
    //由于当时的CoroutineScope纷歧定是根协程;因而必须参加SupervisorJob;不然或许出现CoroutineExceptionHandler无法捕获的bug请注意
    return this.launch(context + SupervisorJob() + CoroutineExceptionHandler { coroutineContext, throwable ->
        //只有当coroutineContext是根coroutineContext时才表明是一个未处理的子协程反常,
        //不然或许是supervisorScope未配置CoroutineExceptionHandler导致的反常传递,此时根协程会进行履行coroutineContext:$coroutineContext ref.get():${ref.get()}", )
        throwable.printStackTrace()
        if (coroutineContext == ref.get()) {
            handleException(throwable, errorHandler, onComplete, onFinally)
        }
    }) {
        ref.set(this.coroutineContext)
        try {
            val result = block()
            onComplete?.invoke(Result.success(result))
            onFinally?.invoke(Result.success(result))
        } catch (e: Exception) {
            if (e is CancellationException) {
                onCancel?.invoke()
                onFinally?.invoke(Result.failure(e))
            } else {
                throw e
            }
        }
    }
}
private fun <T> handleException(
    e: Throwable,
    errorHandler: ((exception: Throwable) -> Unit)?,
    onComplete: ((Result<T>) -> Unit)? = null,
    onFinally:((Result<T>) -> Unit)? = null
) {
    e.printStackTrace()
    //反常处理
    try {
        errorHandler?.invoke(e)
    } catch (e: Exception) {
        e.printStackTrace()
    }
    try {
        onComplete?.invoke(Result.failure(e))
    } catch (e: Exception) {
        e.printStackTrace()
    }
    try {
        onFinally?.invoke(Result.failure(e))
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

代码测试正常反常处理,但实践上这段代码存在逻辑隐患,读者朋友能够细心考虑下,问题出在哪里了;
下面给出反常case,在Android activity oncreate办法中履行以下办法:

private fun testLaunchWithCatch() {
    lifecycleScope.launchWithCatch {
        while (isActive) {
            // do loop operate
            println(">>>>I'm do loop")
            delay(1000)
        }
    }
}

咱们发现即便activity退出了,协程仍然在工作,>>>>I'm do loop会一直输出;
问题原因就在于,SupervisorJob()的参加导致了原先的父子结构发生了变化;他的承继关系是SupervisorJob–〉JobImpl–〉JobSupport–〉Job
回忆下文章开头协程获取设置父Job的场景,代码在类AbstractCoroutine的初始化函数中,

init {
    if (initParentJob) initParentJob(parentContext[Job])
}

运用launchWithCatch导致新生成的协程不再是lifecycleScope的子协程,而是SupervisorJob的子协程,因而当activity页面lifecycleScope cancel时,刚刚建议的协程无法正常封闭;

好了,问题已经知道了,供给解决办法就是让SupervisorJob变成lifecycleScope的子协程即可,代码如下

fun <T> CoroutineScope.launchWithCatch(
...
val parentJob = context[Job] ?: coroutineContext[Job]
return this.launch(context + SupervisorJob(parentJob) + CoroutineExceptionHandler { 
...