[译] Coroutines: First things first

这是关于 协程的取消和异常 的一系列文章,写的很不错。一直准备翻译来着,种种缘由给耽误了,一直拖到如今。android

原文做者:manuelvicntweb

原文地址:Coroutines: First things firstasync

译者:秉心说编辑器

该系列博客深刻探索了协程的取消和异常。取消 能够避免进行预期之外的工做,从而节省内存和电量;合适的异常处理 能够带来良好的用户体验。做为该系列另外两篇文章的基础,经过本文搞清楚协程的一些基本概念,例如 CoroutineScopeJobCoroutineContext 等,是很是重要的。函数

若是你更喜欢视频,能够看看 Florina Muntenescu 和我在 KotlinConf'19 上的演讲。学习

https://www.youtube.com/watch?v=w0kfnydnFWI&feature=emb_logourl

CoroutineScope(协程做用域)

CoroutineScope 能够帮助你追踪任何经过 launchasync 启动的协程。它们都是 CoroutineScope 的扩展函数。正在运行的协程能够经过调用 scope.cancel() 在任意时间点中止。spa

不管你在 App 的任何页面启动协程,并控制其生命周期,都应该建立 CoroutineScope 。在 Android 中,KTX 类库已经为特定的生命周期类提供了 CoroutineScope,例如 viewModelScopelifecycleScope线程

建立 CoroutineScope 时须要给构造函数提供 CoroutineContext(协程上下文) 参数。下面的代码演示了如何新建一个做用域和协程。翻译

// Job 和 Dispatcher 合并在一块儿做为 CoroutineContext,稍后会进行说明
val scope = CoroutineScope(Job() + Dispatchers.Main)

val job = scope.launch {
    // 新协程
}
复制代码

Job

Job 表明了一个协程。经过 launchasync 启动的每个协程,都会返回一个 Job 实例来惟一标识,而且管理该协程的生命周期。如上一节所示,你能够给 CoroutineScope 传递一个 Job 来控制它的生命周期。

CoroutineContext(协程上下文)

能够翻译成协程上下文。但我仍是用英文吧。

CoroutineContext 是定义协程行为的一系列元素。它由如下几部分组成:

  • Job,管理协程的生命周期
  • CoroutineDispatcher,分发任务到合适的线程
  • CoroutineName,协程的名称,用于调试
  • CoroutineExceptionHandler,处理未捕获的异常,这是第三篇文章的内容

一个新协程的 CoroutineContext 是什么?咱们已经知道会建立一个新的 Job 来帮助咱们管理生命周期,剩下的元素将继承自它的父亲的 CoroutineContext (多是另外一个协程,或者是建立它的 CoroutineScope)。

因为 CoroutineScope 能够建立协程,而且你能够在一个协程内部建立多个协程。这就造成了一个隐式的层级结构。在下面的代码中,除了使用 CoroutineScope 建立新协程以外,还展现了如何在一个协程中建立多个协程。

val scope = CoroutineScope(Job() + Dispatchers.Main)

val job = scope.launch {
    // 这里的新协程的父亲是 scope
    val result = async {
        // 这里的新协程的父亲是上面的 scope.launch 启动的协程
    }.await()
}
复制代码

层级结构的根一般是 CoroutineScope 。咱们能够把层级结构想象成下面这样:

Job 生命周期

Job 会经历如下生命周期:

New, Active, Completing, Completed, Cancelling , Cancelled

经过 Job 的这几个属性能够获取它的状态:isActiveisCancelledisCompleted

Job lifecycle
Job lifecycle

当协程处于 Active 状态,失败或者取消都会让协程移动到 Cancelling 状态(isActive = false, isCancelled = true)。当协程中的全部子协程都完成了任务,协程将会进入 Cancelled 状态 (isCompleted = true) 。

关于 Parent CoroutineContext

在协程的继承结构中,每个协程都会有一个父亲,这个父亲多是 CoroutineScope 或者另外一个协程。 可是子协程最终的父 CoroutineContext 可能和其父亲本来的 CoroutineContext 不同。

父 CoroutineContext 的计算公式以下:

Parent context = Defaults + 继承的 CoroutineContext + arguments

其中:

  • 其中一些元素具备默认值: CoroutineDispatcher 的默认值是 Dispatchers.DefaultCoroutineName的默认值是 coroutine
  • 继承的 CoroutineContext 是父亲的 CoroutineContext
  • 传递到协程构建器中的参数优先于继承自上下文的参数

注意:多个 CoroutineContext 能够经过 “+” 操做符合并。因为 CoroutineContext 包含一系列元素,当建立新的 CoroutineContext 时,“+” 右侧的元素将会覆盖左侧的元素。例如:(Dispatchers.Main, “name”) + (Dispatchers.IO) = (Dispatchers.IO, “name”)

经过此协程做用域建立的协程的 CoroutineContext 将至少包含上图中这些元素。CoroutineName 是灰色的,由于它是默认值。

如今咱们知道一个新协程的父 CoroutineContext 是什么了。它本身的 CoroutineContext 其实是这样的:

New coroutine context = parent CoroutineContext + Job()

一般上面的协程做用域建立一个新的协程:

val job = scope.launch(Dispatchers.IO) {
    //新协程
}
复制代码

那么它的父 CoroutineContext 和本身的 CoroutineContext 是什么样的呢?请看下面的图片。

注意上下两个 Job 并非同一个实例,新协程总会获得一个新的 Job 实例。

最终的父 CoroutineContext 的协程调度器是 Dispatchers.IO,由于它被协程构建器中的参数覆盖了。(译者注:scope.launch(Dispatchers.IO)) 。

同时,注意父 CoroutineContext 中的 Job 实例就是 scope 的 Job 实例(红色),而传递到新协程的 CoroutineContext 中的 Job 是一个新的实例(绿色)。

在系列第三篇文章中咱们将看到,CoroutineScope 能够拥有其余的 Job 实现类,SupervisorJob ,它会改变协程做用域的异常处理。所以,在这样的 CoroutineScope 中建立的子协程也将继承 SupervisorJob 类型的 Job 。可是,若是当父协程是另外一个协程的时候,将老是 Job 类型。

如今你已经了解了协程的基础知识,在系列的后面两篇文章中学习更多 取消和异常 的知识吧!


今天的文章就到这里了,这个系列还有三篇文章,都很精彩,扫描下方二维码持续关注吧!

本文使用 mdnice 排版