协程的异常处理1:第一件事

我将翻译三篇介绍协程的 取消异常处理 相关的文章,三篇文章是层层递进的关系。翻译过程中我将尽量忠实于原文。当然,由于水平有限,不能保证完全的翻译正确。如果您发现翻译有错误之处,欢迎在评论里指出。我也将贴出每篇翻译的原文。

那么,这是第一篇。



本系列的博文将深入讨论协程的 取消(cancellation)异常(exception)。取消是很重要的,可以避免做不必要的工作,以避免浪费内存和电量;适当的异常处理是用户体验的关键所在。作为该系列其它篇章的基础,定义好协程的一些核心概念非常重要,比如 CoroutineScopeJobCoroutineContext,以便我们对这些概念有一致的认知。

如果您更喜欢视频,可以查看2019年Kotlin大会上的演讲视频:
KotlinConf 2019: Coroutines! Gotta catch 'em all! by Florina Muntenescu & Manuel Vivo


CoroutineScope

CoroutineScope能跟踪使用 launchasync方法创建的任意协程(这两个方法是 CoroutineScope的扩展方法)。在任意时间点,调用 scope.cancel()方法都可以取消正在执行的协程。

无论何时,当你想在应用的特定层级启动一个协程并且控制它的生命周期,你就应该创建一个 CoroutineScope。在某些平台上,比如Android,就已经有 KTX库可以在特定的生命周期类中提供 CoroutineScope,比如 viewModelScopelifecycleScope

创建一个 CoroutineScope需要 CoroutineContext作为它构造函数的参数。你可以通过下面的代码创建一个新的 scope和协程:

// Job 和 Dispatcher 被组合成一个 CoroutineContext,我们后面会讨论到
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
    //新的协程
}

Job

Job是协程的一个句柄。所有你通过 launchasync创建的协程都会返回一个 Job实例,它可以标识该协程,并且能控制协程的生命周期。在上面的代码中我们可以看到,你可以传递一个 JobCoroutineScope来保持对其生命周期的控制。


CoroutineContext

CoroutineScope是由一系列元素组合而成的,用来定义协程的行为。它的组成元素包括:

  • Job —— 控制协程的生命周期
  • CoroutineDispatcher —— 线程调度器
  • CoroutineName —— 协程的名字,主要用于调试
  • CoroutineExceptionHandler —— 处理未被捕获的异常,我们将在该系列的第三篇中讲到它

一个新协程的 CoroutineContext是什么样子的?我们已经知道它将创建一个新的 Job实例,用于控制其生命周期。它其余的元素将从它的父节点(创建它的另一个协程,或 CoroutineScope)继承而来。

由于 CoroutineScope可以创建协程,并且你可以在一个协程内部创建更多的协程,所以就构建了一个隐式的任务层次结构。在下面的代码片段中,使用 CoroutineScope,除了创建一个新的协程之外,还可以看到在协程内部如何创建更多的协程:

val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
    //scope作为父节点的新协程
    val result = async {
        //由launch创建的协程作为父节点的新协程
    }.await()
}

该层次结构的根节点通常是 CoroutineScope,我们把它可视化如下:

协程在一个任务层次结构中执行,其父节点可以是一个 CoroutineScope,也可以是另一个协程


Job的生命周期

一个 Job可以经历一系列状态:NewActiveCompletingCompletedCancelling以及 Cancelled。我们无法直接访问 Job的状态,但可以访问它的属性:isActiveisCancelled以及 isCompleted。它们之间的关系如下图:

在这里插入图片描述

如果一个协程处于 Active的状态,当协程发生异常或调用了 job.cancel()方法,其 Job的状态将变为 Cancelling(isActive = false, isCancelled = true)。一旦所有子协程的任务都已完成,该协程就会走到 Cancelled状态,并且 isCompleted = true


Parent CoroutineContext

在任务层次结构中,每一个协程都有一个父节点,父节点可能是一个 CoroutineScope,也可能是另一个协程。然而,子协程的 parent CoroutineContext和其父节点的 CoroutineContext可能不一样,它由以下公式计算而成:

Parent context = 默认值 + 继承的CoroutineContext + 传参

  • 一些元素有默认的值,比如 CoroutineDispatcher的默认值是 Dispatchers.DefaultCoroutineName的默认值是 "coroutine"
  • 继承的 CoroutineContext就是创建了该协程的 CoroutineScope另一个协程CoroutineContext
  • 传给协程构建器的参数优先于来自继承的参数。

注意CoroutineContext 可以使用 + 操作符来组合。由于 CoroutineContext是元素的组合,一个新的 CoroutineContext被创建的时候,加号操作符右侧的元素会覆盖操作符左侧的元素。
比如: (Dispatchers.Main, "name") + (Dispatchers.IO) = (Dispatchers.IO, "name")
在这里插入图片描述
每一个由图片中的这个 CoroutineScope启动的协程,其 CoroutineContext里都至少有这些元素。CoroutineName是灰色的因为它使用了默认值。

现在我们知道了什么是一个新协程的 parent CoroutineContext,而新协程自身的 CoroutineContext将是:

新协程的CoroutineContext = parent CoroutineContext + Job()

如果我们用上图中的 CoroutineScope创建一个新的协程,如下:

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

那么它的 parent CoroutineContext和它自身的 CoroutineContext是怎样的呢?从下图中可以看到答案!

在这里插入图片描述
协程 CoroutineContext中的 Job和其 parent CoroutineContext中的 Job永远不会是同一个实例,一个新的协程总会获取一个新的 Job实例

新建协程的 parent CoroutineContext使用了 Dispatchers.IO,而不是 scope中的 CoroutineDispatcher,因为它被协程构建器中的参数覆盖了。并且,parent CoroutineContext中的 Job就是 scopeJob实例(红色),新协程的 CoroutineContext中的 Job是一个新的实例(绿色)。

在该系列文章的第三篇中,我们将看到 CoroutineScopeCoroutineContext中可以有一个不同的 Job实现叫做 SupervisorJob,它将改变 CoroutineScope处理异常的方式。因此,一个使用这种 scope创建的新协程将以 SupervisorJob作为 parent Job。然而,当一个协程的父节点是另一个协程时, parent Job的类型总是 Job(而非 SupervisorJob)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我了解了,下面是对于 Kotlin 协程的使用的简要介绍: Kotlin 协程是一种轻量级的并发编程工具,它利用异步非阻塞的方式来提高应用程序的并发性能。在 Kotlin 中,协程提供了一个简单易用的 API 来实现异步操作,这使得编写异步代码变得更加容易。下面是 Kotlin 协程的使用方法: 1.导入协程库 在 Kotlin 中使用协程需要导入协程库。可以在 build.gradle 文件中添加以下代码: ```kotlin dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:version" } ``` 其中 version 是协程库的版本号。 2.创建协程Kotlin 中,可以使用 `launch` 函数创建一个协程。以下是一个简单的例子: ```kotlin fun main() { GlobalScope.launch { delay(1000) println("World!") } println("Hello,") Thread.sleep(2000) } ``` 在这个例子中,我们使用 `launch` 函数创建了一个协程,它会在 1 秒后输出 "World!"。在这个协程中,我们使用了 `delay` 函数来模拟一些耗时的操作。在主线程中,我们输出了 "Hello,"。最后,我们使用 `Thread.sleep` 函数等待协程执行完毕。 3.协程作用域 在创建协程时,可以指定协程的作用域。例如,可以使用 `runBlocking` 函数创建一个协程作用域,这个作用域会阻塞当前线程直到所有协程执行完毕。以下是一个例子: ```kotlin fun main() = runBlocking<Unit> { val job = launch { delay(1000) println("World!") } println("Hello,") job.join() } ``` 在这个例子中,我们使用 `runBlocking` 函数创建了一个协程作用域。在这个作用域中,我们创建了一个协程,它会在 1 秒后输出 "World!"。在主线程中,我们输出了 "Hello,"。最后,我们使用 `join` 函数等待协程执行完毕。 4.协程取消 在协程执行过程中,可以通过调用 `cancel` 函数来取消协程。例如,以下是一个例子: ```kotlin fun main() = runBlocking<Unit> { val job = launch { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500) } } delay(1300) println("main: I'm tired of waiting!") job.cancel() job.join() println("main: Now I can quit.") } ``` 在这个例子中,我们创建了一个协程,它会重复执行一些操作。在主线程中,我们等待协程执行了 1.3 秒后,取消了协程。最后,我们等待协程执行完毕并输出一些信息。 这就是 Kotlin 协程的基本使用方法。当然,这只是冰山一角,协程还有很多高级用法,例如协程间通信、异常处理等等。如需了解更多信息,请参考 Kotlin 官方文档。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值