美文网首页学习之鸿蒙&Android学习
Kotlin中的协程 - CoroutineContext

Kotlin中的协程 - CoroutineContext

作者: 盛世光阴 | 来源:发表于2021-07-02 16:47 被阅读0次

    前言

    Kotlin是一种在Java虚拟机上运行的静态类型编程语言,被称之为Android世界的Swift,在GoogleI/O2017中,Google宣布Kotlin成为Android官方开发语言

    回顾

    Kotlin中的协程(一) 中我们探讨了以下问题

    协程的特点
    协程的介绍
    协程中的一些元素概念
    如何创建一个协程
    协程不同的调度模式
    协程不同的执行模式
    非阻塞式挂起
    结构化并发

    当我们了解了上述知识点以后,已经对协程有了一些基本的了解,但是对协程其他的的一些问题有点模糊,还是不明白如何使用协程,以及为什么会有协程等其他疑问,本文将继续探讨其他的协程问题

    CoroutineContext

    /**
     * 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].
     */
    @SinceKotlin("1.3")
    public interface CoroutineContext
    

    协程上下文是协程中的重要概念,它管理了协程的生命周期,线程调度,异常处理等功能

    CoroutineContext中的元素

    我们 可以在每个协程块中访问CoroutineContext,它是Element的索引集,可以使用索引Key来访问上下文中的元素

    Job
    public interface Job : CoroutineContext.Element
    协程的后台工作,可用来将协程取消
    默认值: null,比如GlobalScope中的Job为null,

    CoroutineExceptionHandler
    public interface CoroutineExceptionHandler : CoroutineContext.Element
    错误处理

    ContinuationInterceptor
    public interface ContinuationInterceptor : CoroutineContext.Element
    负责线程的输入/输,Dispatcher继承于CoroutineDispatcher,我们通常使用Dispatcher去指定对应的协程执行线程

    CoroutineName
    协程的名称,一般用于调试

    val job = GlobalScope.launch {
        coroutineContext[Job]
        coroutineContext[CoroutineExceptionHandler]
        coroutineContext[ContinuationInterceptor]
        coroutineContext[CoroutineName]
    }
    

    自定义CoroutineContext

    你可以自定义一个Context当启动一个协程时,比如你需要自定义的CoroutineExceptionHandler以及执行线程等

    //自定义CoroutineContext
    val errorHandle = CoroutineExceptionHandler { context, error ->
        Log.e("Mike","coroutine error $error")
    }
    
    //指定自定义CoroutineContext
    GlobalScope.launch(context = errorHandle) {
        delay(2000)
        throw Exception("test")
    }
    打印结果
    coroutine error java.lang.Exception: test
    

    Context也可以支持多个上下文元素的拼接,使用+/plus运算符

    val job = Job()
    val dispatcher = Dispatchers.IO
    val errorHandle = CoroutineExceptionHandler { context, error ->
        Log.e("Mike","coroutine error $error")
    }
    //自定义CoroutineContext集
    val context = job + dispatcher + errorHandle
    GlobalScope.launch(context = context) {
        delay(2000)
        throw Exception("test")
    }
    

    上下文的切换
    可以使用withContext,函数来改变协程的上下文,而仍然驻留在相同的协程中,当执行完withContext块之后又会返回到原始调度中

    GlobalScope.launch {
        val res =  async {
            Log.e("Mike","get respone in ${Thread.currentThread().name}")
            delay(2000)
            "resposne data"
        }
        val response = res.await()
    //执行Dispatchers.Main上下文
        withContext(Dispatchers.Main){
            Log.e("Mike","display respone $response in ${Thread.currentThread().name}")
        }
    }
    打印结果
    get respone in DefaultDispatcher-worker-3
    //两秒后
    display respone resposne data in main
    

    CoroutineContext的创建

    • 默认的CoroutineContext

    我们之前可以直接通过CoroutineScopelaunchasync去创建一个协程,当我们直接使用

    GlobalScope.launch {}
    

    它会有一个默认的EmptyCoroutineContext

    public fun CoroutineScope.launch(
        context: CoroutineContext = EmptyCoroutineContext,
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> Unit
    ): Job
    
    • 手动传入的CoroutineContext
      我们可以自己构建一个CoroutineContext传入进行指定

    • 继承
      当我们在一个协程中创建了另一个子协程的时候,会发生什么,我们可以先看看launch函数的实现

    GlobalScope.launch {
        launch {
    
        }
    }
    

    当子协程创建时调用launch函数,传入子Context或者默认

    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
    }
    

    然后会调用newCoroutineContext(context),将子Context与父coroutineContext进行组装

    public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
        val combined = coroutineContext + context
        val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
        return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
            debug + Dispatchers.Default else debug
    }
    

    这是Context的plus规则,位于CoroutineContext中,也就是说子Context是由父Context和子构建的Context共同决定的,

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

    加号操作符返回一个包含两个上下文所有 elements 的上下文集合,当两个上下文有 key 值相同的 element 时,它将丢弃掉操作符左侧上下文(父)中的 element,也就是子上下文优先,如果子上下文为默认的上下文EmptyCoroutineContext,则返回父上下文

    GlobalScope.launch(Dispatchers.IO + errorHandling) {
        Log.e("Mike","------Parent context--->${coroutineContext[CoroutineExceptionHandler]}---${coroutineContext[ContinuationInterceptor]}")
        launch(Dispatchers.Main) {
            Log.e("Mike","------child context--->${coroutineContext[CoroutineExceptionHandler]}---${coroutineContext[ContinuationInterceptor]}")
        }
    }
    //打印结果
    ------Parent context--->errorHandling---IO
    ------child context--->errorHandling---Main
    

    总结

    • CoroutineContext表示协程的上下文,包含此协程的相关信息,主要包含四个元素JobCoroutineExceptionHandlerContinuationInterceptorCoroutineName它们都实现了CoroutineContext.Element接口
    • CoroutineContext可以自由组装使用运算符+/plus的方式进行拼接,并在启动协程的时候指定
    • 当在协程中刚创建一个子协程时,子协程的上下文则由指定的子协程作用域(或默认)与父上下文组合生成
    • 可以使用withContext切换协程所执行的上下文,执行完毕之后又会回到原始上下文中

    相关文章

      网友评论

        本文标题:Kotlin中的协程 - CoroutineContext

        本文链接:https://www.haomeiwen.com/subject/oncrultx.html