协程学习(六)协程简单使用 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

相关推荐
沐言人生2 小时前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack
追光天使2 小时前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
小雨cc5566ru2 小时前
uniapp+Android智慧居家养老服务平台 0fjae微信小程序
android·微信小程序·uni-app
一切皆是定数3 小时前
Android车载——VehicleHal初始化(Android 11)
android·gitee
一切皆是定数3 小时前
Android车载——VehicleHal运行流程(Android 11)
android
problc3 小时前
Android 组件化利器:WMRouter 与 DRouter 的选择与实践
android·java
图王大胜4 小时前
Android SystemUI组件(11)SystemUIVisibility解读
android·framework·systemui·visibility
服装学院的IT男8 小时前
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2
android
Arms2068 小时前
android 全面屏最底部栏沉浸式
android
服装学院的IT男8 小时前
【Android 源码分析】Activity生命周期之onStop-1
android