协程异常处理(二)

总结

协程中发生的异常都会先沿着 job 体系往上传播,不过不同的 job 有不同的处理逻辑,一般有三种:

  1. 继续往上传递:launch 与 async 属于此咱类型,因此在 launch/async 外面使用 try-catch 无法捕获任何异常。原因是协程内部会 try-catch 住代码中的异常,然后将异常当作参数沿 job 往上传递

    • async 的异常处理方式,跟它们是否是顶层协程有关,具体见示例
  2. 自己处理:supervisorScope 属于此种类型,其收到异常后交由子协程处理,不会往上传递给自己的父协程。可以简单理解为supervisorScope 下的 job 是一个独立的子体系,出异常时完全内部消化

  3. 重新抛出:coroutineScope 属于此种类型,所以可以在 coroutineScope 外面使用 try-catch 捕获其内部所有协程抛出的异常

coroutineScope

其后代协程抛出的异常,都可以在外层使用 try-catch 捕获。如下

kotlin 复制代码
fun test() {
    viewModelScope.launch {
        launch {
            e("launch1")
            try {
                coroutineScope {
                    e("scope1")
                    launch {
                        e("launch2")
                        launch {
                            e("launch3")
                            throw IllegalStateException()
                        }
                    }
                }
            } catch (e:Exception){
                // 可以捕获 launch3 抛出的异常
                e("catch $e")
            }
        }
    }
}
// output
launch1
scope1
launch2
launch3
catch java.lang.IllegalStateException

supervisorScope

阻止异常沿着 Job 体系往上传播,将异常信息交由子协程处理。如下,supervisorScope 后代协程抛出异常会一直传递到 supervisorScope,它会直接交由自己的子协程 launch2 处理,launch2 配置有 handler,所以最终由 handler2 处理。

要注意:supervisorScope 只保存其直接子协程不相互影响,但不能保存所有后代子协程在有异常发生时都不会被取消。如下 launch2 的所有子协程都被取消,但 launch2 的兄弟协程 launch21 并没有。

scss 复制代码
fun test() {
    viewModelScope.launch {
        launch {
            e("launch1")
            try {
                supervisorScope {
                    e("scope1")
                    launch(handler) {
                        e("launch2")
                        launch {
                            e("launch3")
                            launch {
                                e("launch4")
                                throw IllegalStateException()
                            }
                        }
                    }
                    launch {
                        delay(100)
                        e("launch21")
                    }
                }
            } catch (e: Exception) {
                e("catch $e")
            }
        }
    }
}
// output
launch1
scope1
launch2
launch3
launch4
handle java.lang.IllegalStateException
launch21

launch

launch 对异常的处理并没有任何特殊之处,就是往上传播

async

async 对异常的处理分两种情况:

  1. async 启动的是最顶层协程,则会在 await() 中重新抛出,可使用 try-catch 进行捕获
  2. 非顶层协程,会在 await 中抛出(可使用 try-catch 捕获),同时异常会继续往上抛,如果没有对异常进行处理,也会发生崩溃。如下所示,会 catch 住异常,但应用一样会崩溃
kotlin 复制代码
viewModelScope.launch {
    val job = async {
        launch {
            e("launch1")
            throw IllegalStateException()
        }
    }
    try {
        job.await()
    } catch (e: Exception) {
        // 此处会有异常输出,但应用还是会发生 crash,并不能阻止应用崩溃
        e(e.toString())
    }
}

SupervisorJob 与 CoroutineExceptionHandler

子协程会从父协程继承除 job 外的所有 element,包括 excepthonHandler。因此在任何地方添加 handler,其子协程都会拥有 handler,这是理解 exceptionHandler 发生作用的关键。

另外,如果异常传播到最顶层,且最顶层有 exceptionHandler,则异常会交由 handler 处理。

SupervisorJob 的唯一作用就是阻断异常向上传播,让子协程自己处理异常。如果子协程有 exceptionHander,异常就会被交由 exceptionHandler 处理。

如下面代码 scope 中并未定义 handler,但 launch2 中定义有,所以 launch2 的子协程(包括 luanch3) 都会具有 exceptionHandler。在 launch3 使用 SupervisorJob 时后,launch3 的子协程抛出的异常都会被 handler 处理 ------ launch3 继承了 launch2 的 handler

kotlin 复制代码
scope.launch {
    launch(h1) {
        val job = SupervisorJob()
        e("launch2")
        launch(job) {
            e("launch3")
            launch {
                throw IllegalArgumentException()
            }
        }
    }
}

Job 与 CoroutineExceptionHandler

如果在协程中直接 new Job() 并当作参数传递给协程,则该协程及其子协程就是一个完全独立的协程体系,并不受原来的协程体系管理 ------ 也就是说即使旧协程已经取消了,新协程也不会取消,只不过新协程一样会从原来的协程中继承 handler

kotlin 复制代码
val j = viewModelScope.launch {
    launch(handler) {
        val job = Job()
        e("launch2")
        // 使用新 job 构建协程,则新协程是一个完全独立的体系
        // 当 j 取消时,并没有影响到该体系
        // 只不过它依旧继承了 handler,所以发生异常时会触发 handler
        launch(job) {
            e("launch3")
            delay(1000)
            e("launch4")
            launch {
                throw IllegalArgumentException()
            }
        }
    }
    launch {
        e("launch5")
        delay(400)
        e("launch6")
    }
}
Thread.sleep(10)
j.cancel()

// output
launch2
launch5
launch3
launch4
handle java.lang.IllegalArgumentException

Job 与 SupervisorJob

从异常处理来看,Job 与 SupervisorJob 并没有太大区别:都会新成新的独立的协程体系,异常传递到它们后都会调用 handler 对异常进行处理。两者的最大区别在于:子协程是否会被取消

如下 Scope 使用 SupervisorJob 会输出 launch5,使用 Job 则不会输出,这就是 SupervisorJob 的作用:阻止直接子协程被取消

kotlin 复制代码
scope.launch {
    launch {
        delay(10)
        e("launch2 ${System.identityHashCode(handler)}")
        throw IllegalStateException()
    }
    launch {
        e("launch3 ${System.identityHashCode(handler)}")
        delay(1000)
        e("launch4 ${System.identityHashCode(handler)}")
    }
}

scope.launch {
    delay(100)
    e("launch5 ${System.identityHashCode(handler)}")
}

下面是一个易错点:launch 中的 Job 的直接子 Job 是 lambda 对应的 Job,并不是 launch2 对应的 Job

kotlin 复制代码
// launch 中使用 Job 与 SupervisorJob 效果完全一样,launch4 都不会输出
launch(SupervisorJob()) {
    launch {
        delay(10)
        e("launch2")
        throw IllegalStateException()
    }
    launch {
        e("launch3")
        delay(1000)
        e("launch4")
    }
}

// output
launch3
launch2
相关推荐
RickyWasYoung2 分钟前
【matlab】字符串数组 转 double
android·java·javascript
bluetata1 小时前
Rokid AR眼镜开发入门:构建智能演讲提词器Android应用
android·人工智能·云计算·ar·ai编程
马 孔 多 在下雨1 小时前
手机App上的轮播图是如何实现的—探究安卓轮播图
android·智能手机
00后程序员张2 小时前
iOS 26 开发者工具推荐,构建高效调试与性能优化工作流
android·ios·性能优化·小程序·uni-app·iphone·webview
小范馆3 小时前
通过 useEventBus 和 useEventCallBack 实现与原生 Android、鸿蒙、iOS 的事件交互
android·ios·harmonyos
恋猫de小郭3 小时前
Flutter 也有类 React Flow 的节点流程编辑器,快来了解下刚刚开源的 vyuh_node_flow
android·前端·flutter
2501_916008893 小时前
iOS 26 文件导出与数据分析,多工具组合下的开发者实践指南
android·macos·ios·小程序·uni-app·cocoa·iphone
2501_916008893 小时前
iOS混淆实战用多工具组合把IPA加固做成可复用的工程能力(iOS混淆 IPA加固 无源码混淆
android·ios·小程序·https·uni-app·iphone·webview
wangdaoyin20104 小时前
UniApp 在手机端(Android)打开选择文件和文件写入
android·前端·uni-app
我命由我123455 小时前
Android PDF 操作 - AndroidPdfViewer 显示 PDF 异常清单(数据为 null、数据为空、PDF 文件损坏、非 PDF 文件)
android·java·java-ee·pdf·android studio·android-studio·android runtime