[译] 协程中的取消和异常(第 1 部分)- 协程:第一件事

原文:https://medium.com/androiddevelopers/coroutines-first-things-first-e6187bf3bb21


本系列博客文章深入探讨了协程中的取消和异常。取消对于避免做多余的工作很重要,这会浪费内存和电量;正确的异常处理是良好用户体验的关键。作为本系列其他 3 部分(第 2 部分:取消,第 3 部分:异常,第 4 部分:不应取消的工作的协程和模式)的基础,定义一些核心协程概念非常重要,例如 CoroutineScope, Job 和 CoroutineContext 以便我们都在同一页面上。

CoroutineScope 协程作用域

一个 CoroutineScope 跟踪您使用 launch 或 async 创建的任何协程(这些是 CoroutineScope 上的扩展函数)。可以通过 scope.cancel() 在任何时间点调用来取消正在进行的工作(正在运行的协程)。
每当您想启动和控制应用程序特定层中协程的生命周期时,您都应该创建一个 CoroutineScope。在一些像 Android 这样的平台上,有一些 KTX 库已经在某些生命周期类中提供了一个 CoroutineScope,比如 viewModelScope 和 lifecycleScope。
创建一个 CoroutineScope 时,它将一个 CoroutineContext 作为其构造函数的参数。您可以使用以下代码创建一个新的 scope 作用域和 coroutine 协程:

// Job and Dispatcher are combined into a CoroutineContext which
// will be discussed shortly
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
    // new coroutine
}

Job 工作任务

一个 Job 是协程的句柄。对于您(通过 launch 或 async)创建的每个协程,它返回一个唯一标识协程并管理其生命周期的 Job 实例。正如我们在上面看到的,您还可以将一个 Job 传递给一个 CoroutineScope 以保持对其生命周期的句柄。

CoroutineContext 协程上下文

CoroutineContext 是一组定义协程行为的元素。 它由以下组成:

  • Job — 控制协程的生命周期。
  • CoroutineDispatcher — 将工作分派到适当的线程。
  • CoroutineName — 协程的名称,用于调试。
  • CoroutineExceptionHandler — 处理未捕获的异常,将在本系列的第 3 部分中介绍。

什么是新的协程的 CoroutineContext ?我们已经知道将创建一个新 Job 实例,允许我们控制它的生命周期。其余元素将继承其父级(另一个协程或创建它的 CoroutineScope)的 CoroutineContext。
由于一个 CoroutineScope 可以创建协程并且您可以在协程内创建更多协程,因此创建了隐式任务层次结构。在下面的代码片段中,除了使用 CoroutineScope 来创建一个新的协程,看看如何在一个协程中创建更多的协程:

val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
    // New coroutine that has CoroutineScope as a parent
    val result = async {
        // New coroutine that has the coroutine started by 
        // launch as a parent
    }.await()
}

该层次结构的根通常是 CoroutineScope。我们可以将层次结构可视化如下:
在这里插入图片描述
协程在任务层次结构中执行。父级可以是一个 CoroutineScope 或另一个协程。

Job 生命周期

一个 Job 可以经历一组状态:New、Active、Completing、Completed、Canceling 和 Cancelled。虽然我们没有权限访问状态本身,我们可以访问 Job 的属性:isActive,isCancelled 和 isCompleted。
在这里插入图片描述
作业生命周期

如果协程处于活动状态,则协程的失败或 job.cancel() 调用会将作业移至取消状态 ( isActive = false, isCancelled = true)。一旦所有孩子都完成了他们的工作,协程将进入 Canceled 状态并且 isCompleted = true。

父 CoroutineContext 说明

在任务层次结构中,每个协程都有一个父协程,可以是一个 CoroutineScope 协程或另一个协程。但是,一个协程的 CoroutineContext 父级可能与父级(协程)的 CoroutineContext 的不同,因为它是根据以下公式计算的:

Parent context = Defaults + inherited CoroutineContext + arguments

在这里:

  • 某些元素具有默认值:Dispatchers.Default 是 CoroutineDispatcher 的默认值,“coroutine” 是 CoroutineName 的默认值。
  • 继承的 CoroutineContext 是 CoroutineScope 的或者创建它的协程的 CoroutineContext。
  • 在协程构建器中传递的参数将优先于继承上下文中的那些元素。

注意:CoroutineContext 可以使用+运算符组合 。由于 CoroutineContext 是一组元素,因此将使用加号右侧的元素覆盖左侧的元素来创建一个新的 CoroutineContext。例如

(Dispatchers.Main, “name”) + (Dispatchers.IO) = (Dispatchers.IO, “name”)

在这里插入图片描述
这个 CoroutineScope 启动的每个协程至少在 CoroutineContext 中有这些元素。CoroutineName 是灰色的,因为它来自默认值。

现在我们知道一个新协程的父级 CoroutineContext 是什么,它的实际 CoroutineContext 将是:

New coroutine context = parent CoroutineContext + Job()

如果以如上图所示的 CoroutineScope,我们像如下创建一个新的协程:

val job = scope.launch(Dispatchers.IO) {
    // new coroutine
}

该协程的父级 CoroutineContext 及其实际的 CoroutineContext 是什么?请参阅下图中的解决方案!
在这里插入图片描述
CoroutineContext 中的和父级 CoroutineContext 中的 Job 永远不会是同一个实例,因为新的协程总是得到一个 Job 的新实例

父级 CoroutineContext 有 Dispatchers.IO 而不是 scope 的 CoroutineDispatcher,因为它被协程构建器的参数覆盖。此外,检查父级 CoroutineContext 中的 Job 是 scope 的 Job(红色)的实例,并且一个新的 Job(绿色)实例已分配给新协程的实际 CoroutineContext。

正如我们将在本系列的第 3 部分中看到的, 一个 CoroutineScope 在它的 CoroutineContext 中可以有一个不同的 Job 实现,称作 SupervisorJob,它改变了 CoroutineScope 处理异常的方式。因此,使用该 scope 创建的新协程可以以 SupervisorJob 作为父级 Job。但是,当一个协程的父协程是另一个协程时,父级 Job 将始终是 Job 类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值