Kotlin 协程 - 协程上下文 CoroutineContext

一、概念

该接口是一组用来定义协程行为的数据结构,它是有 Key(索引)的 Element(元素)集合,上下文中的每个元素也是上下文(接口Element继承了接口CoroutineContext,因此元素之间可以用+来组合在一起后依然是上下文)。

Job

任务

协程的实例,控制协程的生命周期(new、active、completing、completed、cancelling、cancelled)。

CoroutineDispather

协程调度器

给指定线程分发协程任务(IO、Default、Main、Unconfined)。

CoroutineName

协程名称

调试的时候用(默认为coroutine)。

CoroutineExceptionHandler

协程异常处理器

处理未被捕获的异常。

二、对上下文元素的操作

查找

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

Kotlin中的一个特性:类的名字可被用作它的伴生对象的引用。使用伴生对象作为同名Element的Key更容易记住。

添加

public operator fun plus(context: CoroutineContext): CoroutineContext

不存在对应Key的元素直接被添加,已存在对应Key的元素会被覆盖。

删除

public fun minusKey(key: Key<*>): CoroutineContext

减号操作符没有被重载(含义不够清晰,操作符行为应该与其名称一致)。

折叠

public fun <R> fold(initial: R, operation: (R, Element) -> R): R

在operation中参数R是上一次返回的值(第一次遍历就是初始值),参数element是每次遍历到的上下文元素,操作后返回新的R值。

取消

public fun CoroutineContext.cancel(cause: CancellationException? = null)

取消协程会抛异常,默认可空也可以自定义context.cancel(CancellationException("取消")),底层调用的是Job的取消。

//查找
fun find() {
    val cc: CoroutineContext = CoroutineName("Hello")
    val coroutineName: CoroutineName? = cc[CoroutineName]    //或者 cc.get(CoroutineName)
    println(coroutineName?.name)    //打印:Hello
    println(cc[Job])    //打印:null
}

//添加
fun add() {
    //【情况1】只添加CoroutineName
    val cc1: CoroutineContext = CoroutineName("One")
    println(cc1[CoroutineName]?.name)   //打印:One
    println(cc1[Job]?.isActive)         //打印:null
    //【情况2】只添加Job
    val cc2: CoroutineContext = Job()
    println(cc2[CoroutineName]?.name)    //打印:null
    println(cc2[Job]?.isActive)          //打印:true, 因为 “Active” 是job创建后的初始状态
    //【情况3】合并,未覆盖
    val cc3 = cc1 + cc2
    println(cc3[CoroutineName]?.name)   //打印:One
    println(cc3[Job]?.isActive)          //打印:true
    //【情况4】合并,有覆盖
    val cc4: CoroutineContext = CoroutineName("Four")
    val cc5: CoroutineContext = CoroutineName("Five")
    val cc6 = cc4 + cc5
    println(cc6[CoroutineName]?.name)   //打印:Five
}

//删除
fun delete() {
    val cc1 = CoroutineName("One") + Job()
    println(cc1[CoroutineName]?.name)    //打印:One
    println(cc1[Job]?.isActive)          //打印:true
    val cc2 = cc1.minusKey(CoroutineName)
    println(cc2[CoroutineName]?.name)    //打印:null
    println(cc2[Job]?.isActive)          //打印:true
}

//折叠
fun fold() {
    val ctx = CoroutineName("Hello") + Job()
    val defaultValue = emptyList<CoroutineContext>()
    val finalValue =
        ctx.fold(defaultValue) { acc, element ->    //aac就是上一次返回的集合(第一次为默认值),element是每次遍历到的上下文元素
            acc.plus(element)   //集合添加新值后返回新的集合
        }
    finalValue.forEach(::print)  //打印:CoroutineName(Hello)、JobImpl{Active}@27973e9b
}

三、上下文传递关系

创建协程时,无论是从外部作用域(父协程)中继承的上下文,还是创建时传参指定的上下文,会在调用的 CoroutineScope.newCoroutineContext() 函数中合并为新的上下文(继承的上下文中如果有Element的Key和指定的相同则被覆盖,协程必须运行在一个线程上,未检测出上下文中包含调度器则创建一个default模式的)传递给被创建的协程AbstractCoroutine,将父协程的Job和自己的Job绑定从而实现取消传递,每个被创建协程都拥有自己的 Job 来控制生命周期因此会覆盖掉继承来的。

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): 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
}
public abstract class AbstractCoroutine<in T>(
    parentContext: CoroutineContext,
    initParentJob: Boolean,
    active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
    init {
        //将作用域中的父Job和当前协程Job进行绑定,这样就可以调用scope.cancel()进行取消传递了
        if (initParentJob) initParentJob(parentContext[Job])
    }
    //parentContext就是传递过来的合并上下文,this当前协程作为Job替换掉继承而来的
    public final override val context: CoroutineContext = parentContext + this
}
继承指定
runBlocking( )参数默认值EmptyCoroutineContext。
协程构建器

根协程从调用它的作用域对象中,子协程从父协程的上下文中。

参数默认值EmptyCoroutineContext。
作用域函数从Continuation延续体中获得,而它从父协程中获得(挂起函数只能创建子协程)。withContext()没有默认值必须传参,其它几个函数不支持传参。

四、挂起函数中访问上下文

协程作用域中有 coroutineContext 属性用于访问上下文,对于挂起函数上下文被 continuation 引用并传递,因此可以直接使用 coroutineContext 属性。

suspend fun main() = runBlocking(CoroutineName("main")){
    println(coroutineContext[coutineName]?.name)    //打印:main
    coroutineScope{
        println(coroutineContext[coutineName]?.name)    //打印:main,未指定便继承
        lunch(CoroutineName("new")){
            println(coroutineContext[coutineName]?.name)    //打印:new,指定便覆盖
        }
    }
    delay(10)
}

五、自定义上下文

创建一个实现了 CoroutinContext.Element 接口的类,需要 CoroutineContext.Key<*> 类型的属性作为标识上下文的键,通常的做法是使用该类的伴生对象作为键。

class MyCoroutineContext : CoroutineContext.Element {
    override val key : CoroutineContext.Key<*> = Key
    companion object Key : CoroutineContext.Key<CoroutineContext>
}

六、相关类 

Element上下文元素,继承了CoroutineContext,因此元素本身也是上下文,元素之间用+合并在一起后还是上下文。
AbstractCoroutineContext基本实现类,这个类继承了CombinedContext,实现了上下文元素作为数据结构的接口,所有自定义上下文对象都应该继承自该类
CombinedContext上下文接口的实现类,主要实现了链表数据结构的各种元素操作函数
EmptyCoroutineContext上下文接口空的实现类,表示一个空的链表
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值