Kotlin -协程学习的第三天

146 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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)

image.png

现在我们明白新协程的父级 CoroutineContext 是什么样的了,它实际的 CoroutineContext 是父级 CoroutineContext + Job()

image.png

最终的父级 CoroutineContext 会内含 Dispatchers.IO 而不是 scope 对象里的 CoroutineDispatcher,因为它被协程的 builder 里的参数覆盖了。此外,注意一下父级 CoroutineContext 里的 Job 是 scope 对象的 Job (红色),而新的 Job 实例 (绿色) 会赋值给新的协程的 CoroutineContext。