Kotlin 协程 (五) ——— 协程上下文:CoroutineContext

·  阅读 303

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、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 对象在每个协程中都各不相同,其他元素默认会从父类继承。需要注意的是,每个元素也可以在子协程启动时单独指定,以覆盖父类中的配置。

分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改