本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、CoroutineContext 简介
在之前的文章中,我们介绍过,启动协程时可以通过 start 参数指定其启动模式。今天这篇文章我们将要介绍的是启动协程时可以传入的另一个参数:CoroutineContext,译为协程上下文。
launch 函数源码:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
复制代码
从源码中可以看到,launch 函数只需要三个参数:协程上下文、协程启动模式、协程中需要执行的代码块。其中,前两个参数都有默认值,只有最后一个参数是必需的。协程上下文的默认值是 EmptyCoroutineContext,启动模式的默认值是 CoroutineStart.DEFAULT。
二、CoroutineContext 的组成元素
CoroutineContext 包含以下几项,这些组成元素定义了协程启动时的配置:
- Job:控制协程的生命周期。
- CoroutineDispatcher: 指定分发任务的线程,这一项就是我们已经介绍过的调度器:Dispatchers。默认值是 Dispatchers.Default。
- CoroutineName:指定协程的名称,这一项在调试时比较有用。默认值是 "coroutine"。
- CoroutineExceptionHandler: 指定未被捕获的异常的处理器。
CoroutineContext 的源码中重载了 plus 操作符,所以我们可以用 +
号合并各个元素:
@Test
fun test() = runBlocking<Unit> {
launch(Dispatchers.Default + CoroutineName("hello")) {
println("Current thread: ${Thread.currentThread().name}")
}
}
复制代码
运行程序,输出如下:
Current thread: DefaultDispatcher-worker-1 @hello#2
复制代码
可以看到,线程名字后面跟上了协程的名字。
注:在 main() 方法中和 Android 点击事件中,运行这段代码时,不会打印协程的名字,笔者暂不知道是什么原因。
三、CoroutineContext 的继承
协程的上下文是可以继承的,对于新创建的协程,它的 CoroutineContext 会包含一个全新的 Job 实例,它用来控制协程的生命周期,该协程的其他元素会从 CoroutineContext 的父类继承,父类可能是另外一个协程或者创建该协程的 CoroutineScope。
也就是说,每个协程的上下文中,Job 对象一定是不一样的,而其他元素是继承过来的,都是同一个对象。
@Test
fun test() = runBlocking<Unit> {
val scope = CoroutineScope(Job() + Dispatchers.IO + CoroutineName("test"))
val job = scope.launch {
println("launch ${coroutineContext[Job]} ${coroutineContext[CoroutineName]} ${Thread.currentThread().name}")
launch {
println("launch child 1: ${coroutineContext[Job]} ${coroutineContext[CoroutineName]} ${Thread.currentThread().name}")
}
launch {
println("launch child 2: ${coroutineContext[Job]} ${coroutineContext[CoroutineName]} ${Thread.currentThread().name}")
}
}
job.join()
}
复制代码
在这段代码中,我们用 scope 开启了一个协程,在这个协程中又用 launch 开启了两个子协程,在每个协程中都打印了其上下文中的 Job 对象和 CoroutineName。运行程序,输出如下:
launch "test#2":StandaloneCoroutine{Active}@7e713231 CoroutineName(test) DefaultDispatcher-worker-1 @test#2
launch child 1: "test#3":StandaloneCoroutine{Active}@62f6b2b1 CoroutineName(test) DefaultDispatcher-worker-3 @test#3
launch child 2: "test#4":StandaloneCoroutine{Active}@24945350 CoroutineName(test) DefaultDispatcher-worker-2 @test#4
复制代码
可以看出,Job 对象各不相同,但 CorountineName 是相同的,因为两个子协程的 CoroutineName 都是从父类继承而来。关于 CoroutineExceptionHandler 的内容我们在后续文章中还会讲到,这里暂且不提。
四、小结
本文介绍了协程启动时可以传入的另一个参数 CoroutineContext,它由四个元素组成:Job、CoroutineDispatcher、CoroutineName、CoroutineExceptionHandler,各个元素可以用 +
号连接。
其中,Job 对象在每个协程中都各不相同,其他元素默认会从父类继承。需要注意的是,每个元素也可以在子协程启动时单独指定,以覆盖父类中的配置。