文章目录
CoroutineContext 的加减和获取
CoroutineContext 的合并
我们在使用协程时经常会看到类似这种写法:
fun main() = runBlocking {
// 多个 CoroutineContext 相加,相同的相加有什么效果?
val scope = CoroutineScope(Dispatchers.IO + Job() + Job())
scope.launch {
coroutineContext[Job] // 中括号写的类型,kotlin 好像没这个语法?
}
delay(10000)
}
看到上面的写法不了解原理时你可能会有很多疑问:将两个 CoroutineContext 相加合并效果是怎样的?加法是怎样工作的?优先级是怎样的?两个 Job 相加又是怎样的效果?
在 kotlin 里面有一个概念 operator,可以通过重写操作符实现想要的效果,比如 协程多个 CoroutineContext 相加的 + 号其实是一个函数 plus():
fun main() = runBlocking {
// plus() 和 + 是完全等价的
val scope = CoroutineScope(Dispatchers.IO.plus(Job()))
scope.launch {}
delay(10000)
}
我们看下 plus() 函数具体的实现:
CoroutineContext.kt
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else
context.fold(this) { acc, element ->
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {
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)
}
}
}
简单总结下函数主要的功能逻辑:
- 有多个 CoroutineContext 合并,会两两合并成一个 CombineContext;合并不是按顺序的,合并成新的 CombineContext 时 CoroutineContext 合并的顺序是可能会调整的
fun main() = runBlocking {
// 第一次合并:CombineContext:[Dispatchers.IO, Job()]
// 第二次合并(顺序可能调整):CombineContext:[[Dispatchers.IO, Job()], CoroutineName]
val scope = CoroutineScope(Dispatchers.IO + Job() + CoroutineName("MyCoroutine"))
scope.launch {}
delay(10000)
}
- 有相同类型的 CoroutineContext 合并,最后添加的相同类型的 CoroutineContext 会把之前的替换移除
fun main() = runBlocking {
val job1 = Job()
val job2 = Job()
val scope = CoroutineScope(Dispatchers.IO + job1 + CoroutineName("MyCoroutine") + job2)
println("job1: $job1, job2: $job2")
println("CoroutineContext: ${scope.coroutineContext}")
scope.launch {}
delay(10000)
}
输出结果:
job1:JobImpl{Active}@5b87ed94, job2: JobImpl{Active}@6e0e048a
CoroutineContext:[CoroutineName(MyCoroutine), JobImpl{Active}@6e0e048a, Dispatchers.IO] // job1 被替换了
- 直接合并相同类型的 CoroutineContext 会报错,即是没报错也是新的替换旧的
如果我们尝试直接将两个相同类型的 CoroutineContext 相加,IDE 会报错(不是全部 CoroutineContext 都会报错,比如 CoroutineName):
CoroutineContext 的获取
在协程我们会用 coroutineContext 获取对应的分类,比如 Job、ContinuationInterceptor 等等:
fun main() = runBlocking {
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
coroutineContext[Job]
coroutineContext[ContinuationInterceptor]
}
delay(10000)
}
但是 coroutineContext 的中括号填的是类型,kotlin 没有这个语法。
这个 中括号实际上也是个 operator fun,是一个 get() 函数:
CoroutineContext.kt
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
}
中括号填的也不是一个类型,而是一个 companion object:
public interface Job : CoroutineContext.Element {
// coroutineContext[Job] 填写的 Job 实际上访问的是 Job.Key
public companion objec Key : CoroutineContext.Key<Job>
}
public interface ContinuationInterceptor : CoroutineContext.Element {
// coroutineContext[ContinuationInterceptor] 填写的 Job 实际上访问的是 ContinuationInterceptor.Key
public companion object Key : CoroutineContext.Key<ContinuationInterceptor>
}
那么我们做一个小延伸:如果已知道子类型,怎么通过父类型获取到?比如如果想通过 ContinuationInterceptor 获取 CoroutineDispatcher 要怎么获取?
我们已经知道 CoroutineDispatcher 就是 ContinuationInterceptor,那么可以通过强转的方式;其实 CoroutineDispatcher 也提供了 Key 可以直接填类型获取:
fun main() = runBlocking {
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
// 强转类型获取
val diaptcher: CoroutineDispatcher? = coroutineContext[ContinuationInterceptor] as CoroutineDispatcher
// CoroutineDispatcher 也有 Key
val dispatcher2 = coroutineContext[CoroutineDispatcher]
}
delay(10000)
}
CoroutineContext 的删除
删除某一个 CoroutineContext 是使用的 minusKey() 函数:
fun main() = runBlocking {
val scope = CoroutineScope(Dispatchers.IO + CoroutineName("MyCoroutine") + Job())
scope.launch {
println("coroutineContext: $coroutineContext")
println("coroutineContext after minusKey(): ${coroutineContext.minusKey(Job)}")
}
delay(10000)
}
输出结果:
coroutineContext: [CoroutineName(MyCoroutine), JobImpl{Active}@6e0e048a, Dispatchers.IO]
coroutineContext after minusKey(): [CoroutineName(MyCoroutine), Dispatchers.IO]
自定义 CoroutineContext
协程提供了多种 CoroutineContext,比如 ContinuationInterceptor、Job、CoroutineExceptionHandler,如果我们想给协程附加一些其他属性,可能就需要自定义一个新的 CoroutineContext。
所谓的自定义 CoroutineContext 指的就是创建一个新的 CoroutineContext 类型。
自定义 CoroutineContext 主要有三个步骤:
-
自定义的 CoroutineContext 继承自 AbstractCoroutineContextElement
-
提供 CoroutineContext.Key,让 CoroutineContext 能正常的加减和 get()
-
自定义需要实现的 CoroutineContext 功能
class Logger : AbstractCoroutineContextElement(MyContext) {
companion object Key : CoroutineContext.Key<MyContext>
suspend fun log() {
println("Current coroutine: $coroutineContext")
}
}
fun main() = runBlocking {
val scope = CoroutineScope(EmptyCoroutineContext)
val logger = Logger()
scope.launch(logger) {
coroutineContext[Logger]?.log()
}
delay(10000)
}
总结
1、CoroutineContext 的合并
协程多个 CoroutineContext 相加的 + 号其实是一个函数 plus()。
功能逻辑如下:
-
有多个 CoroutineContext 合并,会两两合并成一个 CombineContext;合并不是按顺序的,合并成新的 CombineContext 时 CoroutineContext 合并的顺序是可能会调整的
-
有相同类型的 CoroutineContext 合并,最后添加的相同类型的 CoroutineContext 会把之前的替换移除
-
直接合并相同类型的 CoroutineContext 会报错,即是没报错也是新的替换旧的
2、CoroutineContext 的获取
coroutineContext[Job] 的中括号实际上也是个 operator fun,是一个 get() 函数;中括号内填的是一个 companion object 的 Key,类型为 CoroutineContext.Key。
3、CoroutineContext 的删除
删除某一个 CoroutineContext 是使用的 minusKey() 函数。
4、自定义 CoroutineContext
所谓的自定义 CoroutineContext 指的就是创建一个新的 CoroutineContext 类型。
自定义 CoroutineContext 主要有三个步骤:
-
自定义的 CoroutineContext 继承自 AbstractCoroutineContextElement
-
提供 CoroutineContext.Key,让 CoroutineContext 能正常的加减和 get()
-
自定义需要实现的 CoroutineContext 功能