一、概念
该接口是一组用来定义协程行为的数据结构,它是有 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 | 上下文接口空的实现类,表示一个空的链表 |