持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
协程的上下文 CoroutineContext
我们知道协程启动需要输入一个重要属性即协程上下文,简单来说是一组用于定义协程行为的元素,它由以下几项构成
- Job: 控制协程的生命周期
- CoroutineDispatcher: 调度器如Dispatchers.IO
- CoroutineName: 协程的名称,调试的时候很有用
- CoroutineExceptionHandler: 用于处理未被捕捉的异常
我们已经知道一个 Job 的实例会被创建,它会帮助我们控制协程的生命周期。而剩下的元素会从 CoroutineContext 的父类继承,该父类可能是另外一个协程或者创建该协程的 CoroutineScope。
由于 CoroutineScope 可以创建协程,而且您可以在协程内部创建更多的协程,因此内部就会隐含一个任务层级。
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
// 新的协程会将 CoroutineScope 作为父级
val result = async {
// 通过 launch 创建的新协程会将当前协程作为父级
}.await()
}
协程上下文的继承
在任务层级中,每个协程都会有一个父级对象,要么是 CoroutineScope 或者另外一个 coroutine。然而,实际上协程的父级 CoroutineContext 和父级协程的 CoroutineContext 是不一样的,因为有如下的公式:
父级上下文 = 默认值 + 继承的 CoroutineContext + 参数
其中:
- 一些元素包含默认值: Dispatchers.Default 是默认的 CoroutineDispatcher,以及 "coroutine" 作为默认的 CoroutineName;
- 继承的 CoroutineContext 是 CoroutineScope 或者其父协程的 CoroutineContext;
- 传入协程 builder 的参数的优先级高于继承的上下文参数,因此会覆盖对应的参数值。
请注意:
- CoroutineContext 可以使用 " + " 运算符进行合并(由于 CoroutineContext 是由一组元素组成的,所以加号右侧的元素会覆盖加号左侧的元素,进而组成新创建的 CoroutineContext)
现在我们明白新协程的父级 CoroutineContext 是什么样的了,它实际的 CoroutineContext 是父级 CoroutineContext + Job()
最终的父级 CoroutineContext 会内含 Dispatchers.IO 而不是 scope 对象里的 CoroutineDispatcher,因为它被协程的 builder 里的参数覆盖了。此外,注意一下父级 CoroutineContext 里的 Job 是 scope 对象的 Job (红色),而新的 Job 实例 (绿色) 会赋值给新的协程的 CoroutineContext。