Koltin协程:First things first

原文

这一系列的博客文章深入讨论了Kotlin协程中的取消和异常。为了避免浪费内存和电池寿命,协程的取消至关重要;正确的异常处理是获得良好用户体验的关键。作为本系列其他两部分(第2部分:取消第3部分:异常)的基础,这篇博客定义一些协程的核心概念,如协程作用域、Job和协程上下文等。

如果你喜欢视频,可以看看KotlinConf'19的这个演讲,作者是 Florina Muntenescu 和我。

CoroutineScope

CoroutineScope _ 会跟踪使用launchasync _ 创建的任何协程(这些是_CoroutineScope的扩展函数),并且可以在任何时间点通过调用scope.cancel()_ 取消正在此上下文中进行的协程。

无论何时你想在你的APP中启动并控制一个协程的生命周期时,都应该创建一个CoroutineScope __ 对象,在Android平台,已有KTX库提供了某些具有生命周期的类的 _ CoroutineScope _ ,如 _ viewModelScope _ 和 _ lifecycleScope _ 。\

在创建 _ CoroutineScope _ 时,它将一个 _ CoroutineContext _ 对象作为构造参数,你可以使用如下代码创建新的协程作用域并使用它启动一个协程:

Job

Job是协程的句柄,对于您通过 launchasync 启动的每个协程,它都会返回一个Job实例,该实例唯一地标识了此协程并管理其生命周期。正如上面的代码,您还可以将Job对象传递给 CoroutineScope 来保持对此协程生命周期的控制。

CoroutineContext

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

  • Job — 控制协程的生命周期。

  • 协程调度器( CoroutineDispatcher )— 将协程分配到适当的线程。

  • CoroutineName — 协程的名称,对调试很有帮助。

  • CoroutineExceptionHandler — 处理未捕获的异常,将在本系列的第3部分中介绍。

当我们创建一个新的协程时,它的协程上下文都有什么呢?我们已经知道协程会返回一个新的_Job_ 实例从而允许我们控制它的生命周期,而其余的元素将从此协程的父元素(父协程或创建它的协程作用域)继承。

由于协程作用域可以创建协程,并且我们可以在协程中创建更多的协程,因此会形成一个隐式的任务层次结构,在下面的代码中,除了使用协程作用域( CoroutineScope )创建一个新的协程外,还可以看看如何在协程中创建更多的协程:

这种层次结构的根通常为最上层的协程作用域( CoroutineScope )。我们可以把该层次结构可视化如下:

Coroutine在一个任务层次中执行。父级可以是CoroutineScope,也可以是另一个coroutine。

Job lifecycle

Job 经历以下一系列状态:新建、活动、正在完成、已完成、正在取消和已取消状态。虽然我们无法访问状态本身,但可以访问_Job_ 的属性: isActiveisCancelledisCompleted

Job生命周期

如果协程处于活动状态,则协程失败或调用job.cancel()方法将使Job处于取消状态( isActive = false, iscancel = true )。一旦所有的子协程完成了他们的工作,协程将进入取消状态并且 isCompleted = true

关于父级CoroutineContext的解释

在协程的任务层次结构中,每个协程都有一个父级,可以是一个协程作用域,也可以是另一个协程。然而,协程继承的父级协程上下文可能不同于父级本身的上下文,因为协程上下文是基于这个公式计算的:

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

  • 某些元素具有默认值,如 Dispatchers.Default 是协程调度器( CoroutineDispatcher )的默认值,“coroutine”是 CoroutineName 的默认值。

  • 协程会继承创建它的协程作用域或协程的 CoroutineContext

  • 在协程构建器中传递的参数会优先于继承的 CoroutineContext 中的元素。

注意: CoroutineContext 可以使用+运算符组合,由于 CoroutineContext 是一组元素,因此会将加号右边的元素覆盖左侧相同的元素以创建一个新的 CoroutineContext ,如 (Dispatchers.Main, “name”) + (Dispatchers.IO) = (Dispatchers.IO, “name”)\

由scope创建的协程,在它的CoroutineContext中至少拥有这些元素,CoroutineName的值为灰色原因是它来自默认值

现在我们知道了一个新的协程的父级协程上下文是什么,那么它实际的协程上下文将是:

新协程的上下文 = 父级CoroutineContext + Job()

If with the CoroutineScope shown in the image above we create a new coroutine like this:

如果使用上图的scope创建一个新协程,就像这样:

那么这个新协程的协程上下文和父级协程上下文将是什么呢?请参见下图的答案!

父级上下文中的Job永远也不会与新协程的上下文的Job是同一实例,因为新协程始终会返回一个新的Job实例

我们看到父级上下文中有 Dispatchers.IO ,而不是声明 scope 时我们传入的Dispatchers.Main ,因为它被协程构建器 launch 的参数覆盖了。另外,请注意父级上下文中的Job实例是scope对象的Job,而新协程的实际Job实例是被重新分配的(绿色)。\

我们将在本系列的第三部分看到,协程上下文中有一个叫 SupervisorJobJob 实现, SupervisorJob 改变了 CoroutineScope 处理异常的方式。因此,用该scope创建的新的 coroutine 可以将 SupervisorJob 作为父Job。然而,当一个协程的父级是协程时,父级Job将始终是Job类型的。

现在你已经知道了协程的基础知识,可以通过本系列的第二部分和第三部分开始学习更多关于协程中的取消和异常。

参考

最后更新于

这有帮助吗?