协程学习(六)协程简单使用 CoroutineContext 组成与继承

CoroutineContext 在协程中是一个接口集,内部包含多个子接口,并且重写了 + [] 这两个方法,方便使用

今天还是使用简单的例子来说明一下 CoroutineContext 组成部分

kotlin 复制代码
@JvmStatic
fun main(a:Array<String>) {
    runBlocking{
        var job=Job()
        var io=Dispatchers.IO
        var name=CoroutineName("tsm")
        var error= CoroutineExceptionHandler{_,e->
            println("-------CoroutineExceptionHandler--------")
            println("Error :$e")
        }
        var continuationContext : CoroutineContext =job + io +name+error
        launch(continuationContext) {
            println("thread Name:${Thread.currentThread().name}")
            delay(1000)
            throw ClassCastException("手动抛出的异常")
        }
        delay(2000)
    }
}

结果:
thread Name:DefaultDispatcher-worker-1 @tsm#2
-------CoroutineExceptionHandler--------
Error :java.lang.ClassCastException: 手动抛出的异常

Process finished with exit code 0

CoroutineContext 通常由4个常见的部分组成

这里会出现一个问题,那就是为什么4个角色可以毫无障碍的组成一个 CoroutineContext ,

先来说一下 CoroutineContext 的继承关系, CoroutineContext 的家族是一个非常强大的家族,他的强大并不是他说的功能非常强大,而是他的子类

kotlin 复制代码
public interface CoroutineContext {

}
public interface Element : CoroutineContext {
       
}


//Name 实现自一个 Element 的子类
public data class CoroutineName(
    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
}

// ErrorHandler 直接实现自 Element
public interface CoroutineExceptionHandler : CoroutineContext.Element {
}

// Dispatcher 的父类也是实现自 Element 的子类
public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
}

// Job直接实现自 Element,
public interface Job : CoroutineContext.Element {
}

可以看到这4种类型间接或直接的实现自 Element,而 Element 继承自 CoroutineContext ,在 协程中基本上所有的重要元素都实现自 CoroutineContext ,再来看看 CoroutineContext 这个接口

kotlin 复制代码
public interface CoroutineContext {

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

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

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

我都看了好多遍了,回过头再看这些东西还是感觉像天书一般,不过看到网上有些大神将 CoroutineContext 与HashMap 做对比,再来看这些方法就非常好理解了

Element 获取

在对比之前先说一下这个Key ,先看一下他的代码,非常的少

kotlin 复制代码
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
}

先定义了一个 Key 类型的接口, 这个接口存放着一个 Element 类型的数据,我们可以使用 Element 通过get方法使用这个key 来获取我们想要的数据(也就是 Element),在 Element 所有 功能子类都有一个 名字是他自己的 伴生对象,这个就是我们用来在组合后的 CoroutineContext 中来获取这个 Element 关键, 这也就解释了 CoroutineContext 的get方法

CoroutineContext 获取

kotlin 复制代码
public fun minusKey(key: Key<*>): CoroutineContext

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

整个获取的方式非常多的简单

CoroutineContext 加法运算

kotlin 复制代码
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)
                }
            }
        }

上面这段代码由于使用 Lambda 表达式,在阅读的过程中非常不好看,不过这里可以理解为 如果plus 传入的是一个 EmptyCoroutineContext就返回this,else 就返回 context.fold 的结果, 而 fold 方法 如下

kotlin 复制代码
    public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
        operation(initial, this)

带入就能知道 context 就是我们的context , 后面的所有代码就是 operation代码块内的代码,最终返回一个R 的类型结果

我们在前几篇文章一直在讲 CoroutineScope 协程作用域,那么这个协程作用域到底是用来干什么的呢, 先来看看他的代码

kotlin 复制代码
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

可以看到他这个类中就一行代码,对外提供 CoroutineContext ,没错 CoroutineScope其实就是一个包装类,他的作用就是将 CoroutineContext 向下传递,那么我们来看看子协程中是都能获取到父协程中的哪些 Element,还是通过一个非常简单里的例子来说明

scss 复制代码
// -Dkotlinx.coroutines.debug
@JvmStatic
fun main(a:Array<String>) {
    runBlocking{
        var job=Job()
        var io=Dispatchers.IO
        var name=CoroutineName("tsm")
        var error= CoroutineExceptionHandler{_,e->
            println("-------CoroutineExceptionHandler--------")
            println("Error :$e")
        }
        println("job:$job")
        println("io:$io")
        println("name:$name")
        println("error:$error")
        var continuationContext : CoroutineContext =job + io +name+error
        launch(continuationContext) {
            launch {
                println("------------------------------------------------")
                println("thread Name:${Thread.currentThread().name}")
                println("get Job:${this.coroutineContext[Job]}")
                println("get io:${this.coroutineContext[CoroutineDispatcher]}")
                println("get name:${this.coroutineContext[CoroutineName]}")
                println("get error:${this.coroutineContext[CoroutineExceptionHandler]}")
                println("------------------------------------------------")
                delay(1000)
                throw ClassCastException("手动抛出的异常")
            }
        }
        delay(2000)
    }
}

结果:
job:JobImpl{Active}@73c6c3b2
io:Dispatchers.IO
name:CoroutineName(tsm)
error:com.tsm.opencv.TsmTest$main$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@48533e64
------------------------------------------------
thread Name:DefaultDispatcher-worker-3 @tsm#3
get Job:"tsm#3":StandaloneCoroutine{Active}@6407b81b
get io:Dispatchers.IO
get name:CoroutineName(tsm)
get error:com.tsm.opencv.TsmTest$main$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@48533e64
------------------------------------------------
-------CoroutineExceptionHandler--------
Error :java.lang.ClassCastException: 手动抛出的异常

Process finished with exit code 0

这段代码我们创建了4个对象分别是 job dispatcher coroutineName ErrorHandler ,并分别打印了一下他们,同时使用他们组合成为一个 CoroutineContext ,并使用它开启了一个协程, 在子协程中 又打印了一遍子协程中的这四个对象

可以看到 除了job 其他3个都是相同的,这同时也验证了我们前面几天在协程作用域中的结论,至于job为什么不相同,原因就是每一个子协程都有他自己的job,如果使用父协程的job 那么子协程将无法管理和调度

这里还会出现一到比较经典的面试题,那就是

launch{} 默认使用的调度器 Dispatchers是哪个, 答案既不是io也不是default,是父携程的调度器,如果你使用的是 viewmodelScope 和 lifecycleScope默认调度器就是Main,GlobalScope就是default

相关推荐
coder_pig2 小时前
🤡 公司Android老项目升级踩坑小记
android·flutter·gradle
死就死在补习班3 小时前
Android系统源码分析Input - InputReader读取事件
android
死就死在补习班3 小时前
Android系统源码分析Input - InputChannel通信
android
死就死在补习班3 小时前
Android系统源码分析Input - 设备添加流程
android
死就死在补习班3 小时前
Android系统源码分析Input - 启动流程
android
tom4i4 小时前
Launcher3 to Launchpad 01 布局修改
android
雨白4 小时前
OkHttpClient 核心配置详解
android·okhttp
淡淡的香烟4 小时前
Android auncher3实现简单的负一屏功能
android
RabbitYao5 小时前
Android 项目 通过 AndroidStringsTool 更新多语言词条
android·python
RabbitYao5 小时前
使用 Gemini 及 Python 更新 Android 多语言 Excel 文件
android·python