Kotlin中的协程 - CoroutineContext

前言

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)