协程中的取消和异常 | 核心概念介绍

在以前的文章里,咱们为各位开发者分享了在 Android 中使用协程的一些基础知识,包括在 Android 协程的 背景介绍上手指南代码实战。本次系列文章 "协程中的取消和异常" 也是 Android 协程相关的内容,咱们将与你们深刻探讨协程中关于取消操做和异常处理的知识点和技巧。html

当咱们须要避免多余的处理来减小内存浪费并节省电量时,取消操做就显得尤其重要;而妥善的异常处理也是提升用户体验的关键。本篇是另外两篇文章的基础 (第二篇和第三篇将为你们分别详解协程取消操做和异常处理), 因此有必要先讲解一些协程的核心概念,好比 CoroutineScope (协程做用域)、Job (任务) 和 CoroutineContext (协程上下文),这样咱们才可以进行更深刻的学习。android

CoroutineScope

CoroutineScope 会追踪每个您经过 launch 或者 async 建立的协程 (这两个是 CoroutineScope 的扩展函数)。任什么时候候均可经过调用 scope.cancel() 来取消正在进行的工做 (正在运行的协程)。git

当您但愿在应用程序的某一个层次开启或者控制协程的生命周期时,您须要建立一个 CoroutineScope。对于一些平台,好比 Android,已经有 KTX 这样的库 在一些类的生命周期里提供了 CoroutineScope,好比 viewModelScope.viewModelScope:kotlinx.coroutines.CoroutineScope) 和 lifecycleScopegithub

当建立 CoroutineScope 的时候,它会将 CoroutineContext 做为构造函数的参数。您能够经过下面代码建立一个新的 scope 和协程:api

//Job 和 Dispatcher 已经被集成到了 CoroutineContext
//后面咱们详细介绍
val scope = CoroutineScope(Job() + Dispatchers.Main)

val job = scope.launch {
    //新的协程
}

Job

Job 用于处理协程。对于每个您所建立的协程 (经过 launch 或者 async),它会返回一个 Job 实例,该实例是协程的惟一标识,而且负责管理协程的生命周期。正如咱们上面看到的,您能够将 Job 实例传递给 CoroutineScope 来控制其生命周期。jvm

CoroutineContext

CoroutineContext 是一组用于定义协程行为的元素。它由以下几项构成:async

  • Job: 控制协程的生命周期;
  • CoroutineDispatcher: 向合适的线程分发任务;
  • CoroutineName: 协程的名称,调试的时候颇有用;
  • CoroutineExceptionHandler: 处理未被捕捉的异常,在将来的第三篇文章里会有详细的讲解。

那么对于新建立的协程,它的 CoroutineContext 是什么呢?咱们已经知道一个 Job 的实例会被建立,它会帮助咱们控制协程的生命周期。而剩下的元素会从 CoroutineContext 的父类继承,该父类多是另一个协程或者建立该协程的 CoroutineScope。函数

因为 CoroutineScope 能够建立协程,并且您能够在协程内部建立更多的协程,所以内部就会隐含一个任务层级。在下面的代码片断中,除了经过 CoroutineScope 建立新的协程,来看看如何在协程中建立更多协程:学习

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

val job = scope.launch {
    // 新的协程会将 CoroutineScope 做为父级
    val result = async {
        // 经过 launch 建立的新协程会将当前协程做为父级
    }.await()
}

层级的根一般是 CoroutineScope。图形化该层级后以下图所示:ui

△ 协程是以任务层级为序执行的。

△ 协程是以任务层级为序执行的。

父级是 CoroutineScope 或者其它协程

Job 的生命周期

一个任务能够包含一系列状态: 新建立 (New)、活跃 (Active)、完成中 (Completing)、已完成 (Completed)、取消中 (Cancelling) 和已取消 (Cancelled)。虽然咱们没法直接访问这些状态,可是咱们能够访问 Job 的属性: isActive、isCancelled 和 isCompleted。

△ Job 的生命周期

若是协程处于活跃状态,协程运行出错或者调用 job.cancel() 都会将当前任务置为取消中 (Cancelling) 状态 (isActive = false, isCancelled = true)。当全部的子协程都完成后,协程会进入已取消 (Cancelled) 状态,此时 isCompleted = true。

解析父级 CoroutineContext

在任务层级中,每一个协程都会有一个父级对象,要么是 CoroutineScope 或者另一个 coroutine。然而,实际上协程的父级 CoroutineContext 和父级协程的 CoroutineContext 是不同的,由于有以下的公式:

父级上下文 = 默认值 + 继承的 CoroutineContext + 参数

其中:

  • 一些元素包含默认值: Dispatchers.Default 是默认的 CoroutineDispatcher,以及 "coroutine" 做为默认的 CoroutineName;
  • 继承的 CoroutineContext 是 CoroutineScope 或者其父协程的 CoroutineContext;
  • 传入协程 builder 的参数的优先级高于继承的上下文参数,所以会覆盖对应的参数值。
请注意: CoroutineContext 可使用 " + " 运算符进行合并。因为 CoroutineContext 是由一组元素组成的,因此加号右侧的元素会覆盖加号左侧的元素,进而组成新建立的 CoroutineContext。好比,(Dispatchers.Main, "name") + ( Dispatchers.IO) = ( Dispatchers.IO, "name")。

△ 该 CoroutineScope 所建立的每个协程,CoroutineContext 至少会包含这些元素。

△ 该 CoroutineScope 所建立的每个协程,CoroutineContext 至少会包含这些元素。

这里的 CoroutineName 是灰色的,由于该值源于默认参数值。

那么如今咱们明白新协程的父级 CoroutineContext 是什么样的了,它实际的 CoroutineContext 是:

新的 CoroutineContext = 父级 CoroutineContext + Job()

若是使用上图中的 CoroutineScope ,咱们能够像下面这样建立新的协程:

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

而该协程的父级 CoroutineContext 和它实际的 CoroutineContext 是什么样的呢?请看下面这张图。

△ CoroutineContext 里的 Job 和父级上下文里的不多是经过一个实例,由于新的协程总会拿到一个 Job 的新实例

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

在咱们这个系列的第三部分中,CoroutineScope 会有另一个 Job 的实现称为 SupervisorJob 被包含在其 CoroutineContext 中,该对象改变了 CoroutineScope 处理异常的方式。所以,由该 scope 对象建立的新协程会将一个 SupervisorJob 做为其父级 Job。不过,当一个协程的父级是另一个协程时,父级的 Job 会仍然是 Job 类型。

如今,你们了解了协程的一些基本概念,在接下来的文章中,咱们将在第二篇继续深刻探讨协程的取消、第三篇探讨协程的异常处理,感兴趣的读者请继续关注咱们的更新。

相关文章
相关标签/搜索