当前位置: 首页 > article >正文

Kotlin 协程的异常处理

1. 一个协程突然失败了!我该怎么办?

在协程中,如果某个协程发生异常,它会传播到其父级协程,导致父协程和所有子协程的取消。这种异常传播机制在有些场景下非常有用,但也可能带来一些问题。

协程异常传播的基本流程:

  1. 子协程发生异常,异常会传播到父级协程。
  2. 父协程取消其他子协程。
  3. 父协程自己也会被取消,并且异常继续向上传播。

问题:如果某个子协程失败,整个父协程及其他兄弟协程都会被取消,这会影响其他正常工作的协程。

2. SupervisorJob 来拯救你

为了解决协程异常传播带来的问题,我们可以使用 SupervisorJob。它允许我们在一个协程发生异常时,其他兄弟协程不受影响,父协程也不会被取消。

示例代码:

val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

scope.launch {
    val deferred1 = async {
        log("hello")
        delay(300)
        throw IllegalStateException("hello")
    }
    val deferred2 = async {
        log("world")
        delay(10000)
        log("卧槽")
    }
    deferred1.await() // Will throw
    deferred2.await()
    log("哈哈")
}

输出:

hello
world
后续的日志没出现,应用崩溃

使用 SupervisorJob 后,子协程失败不会影响其他子协程的执行。

3. Job or SupervisorJob? 

  • Job:使用 Job 时,异常会传播到父协程,导致父协程和所有子协程被取消。
  • SupervisorJob:使用 SupervisorJob 时,父协程和兄弟协程不会受到影响,异常不会传播。

何时使用:

  • 使用 Job:当你希望父协程在子协程失败时被取消时。
  • 使用 SupervisorJob:当你希望一个子协程失败不会影响其他子协程时。

示例:

val scope = CoroutineScope(SupervisorJob())

scope.launch {
    // Child 1
}

scope.launch {
    // Child 2
}

在这种情况下,即使 Child 1 失败,Child 2 也不会被取消。

4. 协程的 parent 是谁?

协程的父协程是在启动协程时通过 CoroutineScopesupervisorScope 设置的。当你使用 SupervisorJob 创建协程时,这个 SupervisorJob 的行为仅在它是父协程时才有效。如果将它作为构造器参数传递给其他协程,它就不会发挥作用。

示例:

val scope = CoroutineScope(Job())

scope.launch(SupervisorJob()) {
    // Parent is Job, not SupervisorJob
    launch {
        // Child 1
    }

    launch {
        // Child 2
    }
}

注意:SupervisorJob 只有在它作为 CoroutineScope 的一部分时才会生效。

5. 底层原理

如果你对 JobSupervisorJob 的工作原理感兴趣,可以查看 JobSupport.kt 文件中的 childCancellednotifyCancelling 函数的实现。在 SupervisorJob 中,childCancelled 返回 false,表示它不会传播取消,但不会处理异常。

6. 处理异常 

Kotlin 协程中有几种方法可以捕获和处理异常:

launch

使用 launch 启动协程时,异常会立即抛出。如果你不想让异常终止协程,可以用 try-catch 捕获异常:

scope.launch {
    try {
        codeThatCanThrowExceptions()
    } catch (e: Exception) {
        // Handle exception
    }
}

async

使用 async 时,异常不会立刻抛出,只有在调用 await() 时,异常才会被抛出。处理 async 的异常,通常需要在 await() 调用时加上 try-catch

supervisorScope {
    val deferred = async {
        codeThatCanThrowExceptions()
    }
    try {
        deferred.await()
    } catch (e: Exception) {
        // Handle exception thrown in async
    }
}

CoroutineExceptionHandler

CoroutineExceptionHandler 是一个可选的 CoroutineContext 参数,可以帮助你处理未捕获的异常。它的作用类似于 Thread.UncaughtExceptionHandler

val handler = CoroutineExceptionHandler { context, exception ->
    println("Caught $exception")
}

val scope = CoroutineScope(Job())
scope.launch(handler) {
    launch {
        throw Exception("Failed coroutine")
    }
}

launch 的子协程发生异常时,CoroutineExceptionHandler 会捕获并处理该异常。

7. 总结

  • 异常处理在协程中至关重要,正确地捕获和处理异常能避免应用崩溃,并提高用户体验。
  • 使用 SupervisorJob 可以避免异常传播到父协程,确保兄弟协程不受影响。
  • CoroutineExceptionHandler 是处理未捕获异常的有力工具,可以捕获 launch 类型的协程中的异常,但不能捕获 async 类型协程的异常,除非在 await 调用时捕获。

理解并正确使用这些工具将帮助你更好地管理协程中的异常,提供一个更加稳健和友好的应用程序。

🌟 关注我的CSDN博客,收获更多技术干货! 🌟


http://www.kler.cn/a/420849.html

相关文章:

  • 【Gitlab】gitrunner并发配置
  • 如何预防服务器后台爆破攻击
  • Debian 的更新原理
  • 洛谷P2084
  • 学习笔记052——Spring Boot 自定义 Starter
  • 云服务器和物理服务器租用哪个好?
  • 蓝象智联携手西电发布GaiaGPT,夯实数据安全底座
  • python(18) : flask_sqlalchemy 配置sqlserver数据库对象
  • 设计模式之 建造者模式 C# 范例
  • 细说STM32单片机用定时器触发DAC输出三角波并通过串口观察波形的方法
  • Microi吾码产品深度测评:轻量级企业管理应用的全方位剖析
  • Python生日祝福烟花
  • 怎么使用开源的 FFmpeg 命令行工具压缩视频大小
  • 【贪心算法】贪心算法五
  • “量子跃迁与数据织网:深入探索K最近邻算法在高维空间中的优化路径、神经网络融合技术及未来机器学习生态系统的构建“
  • java网络通信(三):TCP通信、实现客户端-服务端消息通信
  • 详细介绍下oracle建库过程中核心脚本dbcore.bsq
  • Linux系统编程之进程控制
  • 华为的USG6000为什么不能ping通
  • 微信小程序 运行出错 弹出提示框(获取token失败,请重试 或者 请求失败)
  • 深入探索HarmonyOS next与ArkTS探索
  • Ubuntu桥接模式设置静态IP
  • 【错误记录】Android Studio 开发环境内存占用过多 ( 记录内存使用情况 )
  • 【系统架构设计师】真题论文: 论无服务器架构及其应用(包括解题思路和素材)
  • 在物理机上安装 Jupyter 的完整指南
  • Spark 内存管理机制