You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android ViewModel中Kotlin协程的异常处理行为疑问

Kotlin协程:supervisorScope与coroutineScope的异常处理差异解析

一、为什么supervisorScope的异常无法被外层try-catch捕获,而coroutineScope可以?

核心差异在于两者的异常传播规则完全不同:

  • coroutineScope是结构化并发的默认「协同取消作用域」:
    当任意子协程抛出未捕获异常时,会立即取消所有兄弟协程,随后终止自身作用域,并将异常向上传播给父协程。此时外层的try-catch包裹的是coroutineScope的代码块,异常会从coroutineScope代码块抛出,因此能被捕获。

  • supervisorScope是专门设计的「监督作用域」,核心是让子协程异常互不影响:
    子协程抛出未捕获异常时,只会取消自身,不会波及兄弟协程;但supervisorScope不会将子协程的异常捕获并抛出到自身代码块中,而是直接将异常传递给子协程上下文的CoroutineExceptionHandler。如果没有自定义处理器,异常会交给全局默认异常处理器(比如Android的崩溃监控、JVM的默认异常处理),最终还会传递到父协程(比如你的viewModelScope.launch协程),导致父协程被取消,引发应用崩溃。

回到你的代码:

viewModelScope.launch {
    try {
        supervisorScope {
            launch {
                throw Exception()
            }
        }
    } catch (e: Exception) {
        println("Exception caught")
    }
}

异常从supervisorScope内部的子协程直接跳过了supervisorScope的代码块,传递给了父协程,而外层try-catch仅包裹了supervisorScope的执行代码,自然捕获不到这个异常。换成coroutineScope时,异常会从coroutineScope代码块抛出,因此能被外层捕获。

二、为什么runBlocking下两种场景的进程退出码不同?

Case 2:直接在runBlocking中launch子协程抛异常

runBlocking {
    launch {
        throw Exception("Launch Exception")
    }
}

runBlocking是阻塞式作用域,会等待所有子协程完成。内部launch的子协程抛出未捕获异常时,会直接传播给runBlocking作用域,runBlocking会将该异常重新抛到JVM主线程——这属于未捕获的顶层异常,会导致JVM进程以**非0退出码(1)**终止,标记进程异常崩溃。

Case 1:runBlocking中嵌套supervisorScope的子协程抛异常

runBlocking {
    supervisorScope {
        launch {
            throw Exception("Supervisor launch exception")
        }
    }
}

supervisorScope的子协程抛出异常时,不会导致supervisorScope本身失败,异常仅会被全局默认异常处理器处理(控制台会打印栈跟踪),但supervisorScope会正常执行完成。runBlocking等待supervisorScope结束后,没有收到任何异常,因此进程正常退出,返回0退出码。

总结:

  • Case2的异常直接终止了runBlocking的执行流程,导致进程异常退出
  • Case1的异常仅被默认处理器处理,未打断runBlocking的正常执行,因此进程正常退出

内容的提问来源于stack exchange,提问作者Shyam Patel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 16:57:47