CoroutineContext 在协程中是一个接口集,内部包含多个子接口,并且重写了 + [] 这两个方法,方便使用
今天还是使用简单的例子来说明一下 CoroutineContext 组成部分
kotlin
@JvmStatic
fun main(a:Array<String>) {
runBlocking{
var job=Job()
var io=Dispatchers.IO
var name=CoroutineName("tsm")
var error= CoroutineExceptionHandler{_,e->
println("-------CoroutineExceptionHandler--------")
println("Error :$e")
}
var continuationContext : CoroutineContext =job + io +name+error
launch(continuationContext) {
println("thread Name:${Thread.currentThread().name}")
delay(1000)
throw ClassCastException("手动抛出的异常")
}
delay(2000)
}
}
结果:
thread Name:DefaultDispatcher-worker-1 @tsm#2
-------CoroutineExceptionHandler--------
Error :java.lang.ClassCastException: 手动抛出的异常
Process finished with exit code 0
CoroutineContext 通常由4个常见的部分组成
这里会出现一个问题,那就是为什么4个角色可以毫无障碍的组成一个 CoroutineContext ,
先来说一下 CoroutineContext 的继承关系, CoroutineContext 的家族是一个非常强大的家族,他的强大并不是他说的功能非常强大,而是他的子类
kotlin
public interface CoroutineContext {
}
public interface Element : CoroutineContext {
}
//Name 实现自一个 Element 的子类
public data class CoroutineName(
val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
}
// ErrorHandler 直接实现自 Element
public interface CoroutineExceptionHandler : CoroutineContext.Element {
}
// Dispatcher 的父类也是实现自 Element 的子类
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
}
// Job直接实现自 Element,
public interface Job : CoroutineContext.Element {
}
可以看到这4种类型间接或直接的实现自 Element,而 Element 继承自 CoroutineContext ,在 协程中基本上所有的重要元素都实现自 CoroutineContext ,再来看看 CoroutineContext 这个接口
kotlin
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
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)
}
}
}
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext {
public val key: Key<*>
public override operator fun <E : Element> get(key: Key<E>): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
}
}
我都看了好多遍了,回过头再看这些东西还是感觉像天书一般,不过看到网上有些大神将 CoroutineContext 与HashMap 做对比,再来看这些方法就非常好理解了
Element 获取
在对比之前先说一下这个Key ,先看一下他的代码,非常的少
kotlin
public interface Key<E : Element>
public interface Element : CoroutineContext {
public val key: Key<*>
public override operator fun <E : Element> get(key: Key<E>): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
}
先定义了一个 Key 类型的接口, 这个接口存放着一个 Element 类型的数据,我们可以使用 Element 通过get方法使用这个key 来获取我们想要的数据(也就是 Element),在 Element 所有 功能子类都有一个 名字是他自己的 伴生对象,这个就是我们用来在组合后的 CoroutineContext 中来获取这个 Element 关键, 这也就解释了 CoroutineContext 的get方法
CoroutineContext 获取
kotlin
public fun minusKey(key: Key<*>): CoroutineContext
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
整个获取的方式非常多的简单
CoroutineContext 加法运算
kotlin
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)
}
}
}
上面这段代码由于使用 Lambda 表达式,在阅读的过程中非常不好看,不过这里可以理解为 如果plus 传入的是一个 EmptyCoroutineContext就返回this,else 就返回 context.fold 的结果, 而 fold 方法 如下
kotlin
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
带入就能知道 context 就是我们的context , 后面的所有代码就是 operation代码块内的代码,最终返回一个R 的类型结果
我们在前几篇文章一直在讲 CoroutineScope 协程作用域,那么这个协程作用域到底是用来干什么的呢, 先来看看他的代码
kotlin
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
可以看到他这个类中就一行代码,对外提供 CoroutineContext ,没错 CoroutineScope其实就是一个包装类,他的作用就是将 CoroutineContext 向下传递,那么我们来看看子协程中是都能获取到父协程中的哪些 Element,还是通过一个非常简单里的例子来说明
scss
// -Dkotlinx.coroutines.debug
@JvmStatic
fun main(a:Array<String>) {
runBlocking{
var job=Job()
var io=Dispatchers.IO
var name=CoroutineName("tsm")
var error= CoroutineExceptionHandler{_,e->
println("-------CoroutineExceptionHandler--------")
println("Error :$e")
}
println("job:$job")
println("io:$io")
println("name:$name")
println("error:$error")
var continuationContext : CoroutineContext =job + io +name+error
launch(continuationContext) {
launch {
println("------------------------------------------------")
println("thread Name:${Thread.currentThread().name}")
println("get Job:${this.coroutineContext[Job]}")
println("get io:${this.coroutineContext[CoroutineDispatcher]}")
println("get name:${this.coroutineContext[CoroutineName]}")
println("get error:${this.coroutineContext[CoroutineExceptionHandler]}")
println("------------------------------------------------")
delay(1000)
throw ClassCastException("手动抛出的异常")
}
}
delay(2000)
}
}
结果:
job:JobImpl{Active}@73c6c3b2
io:Dispatchers.IO
name:CoroutineName(tsm)
error:com.tsm.opencv.TsmTest$main$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@48533e64
------------------------------------------------
thread Name:DefaultDispatcher-worker-3 @tsm#3
get Job:"tsm#3":StandaloneCoroutine{Active}@6407b81b
get io:Dispatchers.IO
get name:CoroutineName(tsm)
get error:com.tsm.opencv.TsmTest$main$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@48533e64
------------------------------------------------
-------CoroutineExceptionHandler--------
Error :java.lang.ClassCastException: 手动抛出的异常
Process finished with exit code 0
这段代码我们创建了4个对象分别是 job dispatcher coroutineName ErrorHandler ,并分别打印了一下他们,同时使用他们组合成为一个 CoroutineContext ,并使用它开启了一个协程, 在子协程中 又打印了一遍子协程中的这四个对象
可以看到 除了job 其他3个都是相同的,这同时也验证了我们前面几天在协程作用域中的结论,至于job为什么不相同,原因就是每一个子协程都有他自己的job,如果使用父协程的job 那么子协程将无法管理和调度
这里还会出现一到比较经典的面试题,那就是
launch{} 默认使用的调度器 Dispatchers是哪个, 答案既不是io也不是default,是父携程的调度器,如果你使用的是 viewmodelScope 和 lifecycleScope默认调度器就是Main,GlobalScope就是default