跳至主要內容

构建协程

guodongAndroid大约 5 分钟

构建协程

您现在已经多次听说过启动协程这个术语。实际上,这是您第一次使用协程构建器。Coroutines 库提供几个协程构建器函数用来启动一个新的协程。在前面的示例中,您使用了带有一下函数签名的 launch 构建器:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job

如您所见,launch 需要您传入以下几个参数:一个 CoroutineContext,一个 CoroutineStart 以及一个 lambda 函数,它定义了当你启动协程时会发生什么。前两个参数是可选的。此函数返回一个 Job 类型。

CoroutineContext 是有关当前协程的上下文信息的持久数据集。它可以包含协程的 Job 和 Dispatcher 等对象,稍后您将了解这两个对象。由于您没有在上面的代码片段中指定任何上下文,因此它将使用默认的 EmptyCoroutineContext,它指向指定的 CoroutineScope 使用的任何上下文。如果您愿意,您可以创建自定义上下文,但在大多数情况下,现有的就足够了。

CoroutineStart 指定您启动协程的模式。有以下模式可以选择:

  • DEFAULT:根据上下文立即调度一个协程执行。
  • LAZY:延迟启动协程。
  • ATOMIC:与 DEFAULT 类似,不同的是,在协程启动前无法取消。
  • UNDISPATCHED:运行协程直到遇见它的第一个挂起点。

您传入的 lambda 块是协程将执行的代码。如果你仔细检查前面 launch 的定义会发现这个 lambda 块与标准的 lambda 块有一些不同。首先是签名的不同,这块 lambda 的签名是:block: suspend CoroutineScope.() -> Unit 。其次,它是一个带有 CoroutineScope 类型接收者的 lambda。它允许您可以嵌套 Job,因此您可以从另一个 launch 块中启动更多协程。最后,它还有确切的 suspend 修饰符。

正如您所了解的,协程基于挂起函数的概念。您可以使用修饰符手动标记一个 lambda 或函数为可挂起的。您将在下一章了解更多关于挂起函数的知识。

最后,一旦您成功启动了一个协程,你就会接收一个 Job。 顾名思义,Job 是协程封装的产物,并且让您可以控制协程的一些工作。

在我们分析 Jobs 是如何工作的之前,我们必须绕道而行,深入了解 CoroutineScope 的作用!

协程作用域

如您所知,协程可以与程序的主程序并行启动。但是,这并不意味着如果主程序完成或停止,协程也会随之完成或停止。或者至少在协程 API 的前几个版本中没有这样做。即使您关闭了应用程序,应用程序也会执行任务,这种行为会导致一些的错误。

为了缓解这些情况,Coroutines API 团队创建了 CoroutineScope。每个作用域实例都知道它与哪个上下文相关,并且每个作用域都有自己的生命周期。如果您选择作用域的生命周期已经结束,当它尝试运行协程时,所有工作,即使正在进行的,也将停止。这就是为什么如果您尝试在没有 Thread.sleep 的情况下运行前面的代码片段,可能没有任何输出或可能只有一些输出。

因此,您必须在 CoroutineScope 上调用 launch,有两种方法可以实现。您可以使用 GlobalScope,就像您到目前为止所做的那样,而不关心协程的确切启动位置。或者您可以实现 CoroutineScope 接口,并提供一个您将运行协程的 CoroutineContext 实例。前者相对简单一些,当您不关心协程的结果、切换到 UI 线程或工作完成时,这是一个不错的选择。如果您想指定需要在何处使用结果(如 UI 线程)以及何时要将 Job 绑定到某个对象实例的生命周期上(如 Android 中的 Activity 实例)时,则后者是至关重要的。

在某些情况下,生命周期或手动取消不一定会取消协程。提供取消机制不仅很重要,而且编写协作代码也很重要。这意味着您的函数会检查其包装 Job 是否正在运行。您将在本章后面看到如何做到这一点。

提示

注意:如果您想避免在使用 GlobalScope 时出现警告,您可以使用 @DelicateCoroutinesApi 标记您使用它的函数。 这标志着您的功能是微妙的,人们在调用时需要留意。这并不意味着 GlobalScope 不好,而是您应该避免一直使用它!它对于您希望确保在应用程序处于活动状态时运行或希望在应用程序完成之前触发并忘记的操作非常有用 - 例如下载文件或在您不需要结果的某些数据之间同步。

您应该更好地了解协程的工作原理以及启动协程时定义的重要内容。在接下来的几节中,您将了解更多关于协程具有的不同功能性的信息,您将了解如何使用 launch 组合运行几个不同任务的 Jobs。