3.6-CoroutineScope/CoroutineContext:CoroutineContext 的加减、获取和自定义 CoroutineContext

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 功能

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值