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

Kotlin协程中CoroutineExceptionHandler与try-catch无法捕获异常

Kotlin协程async异常捕获失效问题排查与解决

问题描述

使用Kotlin协程时,async块内抛出的异常既无法被CoroutineExceptionHandler捕获,也无法被try-catch块捕获。复现代码如下:

import kotlinx.coroutines.*

fun log(message: String) {
    println("[${Thread.currentThread()}] $message")
}

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Caught $exception")
    }
    val scope = CoroutineScope(handler)
    val task = scope.async() {
        log("throw exception")
        throw RuntimeException("Exception")
    }
    scope.launch(handler) {
        try {
            task.await()
        } catch (e: Exception) {
            log("catch exception: ${e.message}")
        }
        delay(1000)
        log("finish await")
    }
    delay(2000)
}

运行后控制台仅输出:

[Thread[DefaultDispatcher-worker-1,5,main]] throw exception

程序无异常捕获日志输出便直接结束。

异常未被捕获的核心原因

  1. 普通Job作用域下的异常立即传播:你使用的CoroutineScope(handler)默认绑定普通Job,async协程抛出异常时,会立即将异常传播到父作用域的Job,导致整个作用域标记为失败状态,并取消所有子协程(包括后续创建的launch协程)。因此scope.launch里的try-catch和await()逻辑根本没有执行的机会。

  2. CoroutineExceptionHandler未触发的原因:虽然你给作用域配置了异常处理器,但由于runBlocking作用域和自定义scope相互独立,且未等待自定义scope的协程完成,处理器的输出可能在程序结束前未被执行或缓冲,导致你看不到捕获日志。

正确的异常捕获方法

方法1:用SupervisorJob隔离子协程异常

SupervisorJob会阻止子协程的异常传播到父作用域,也不会影响其他子协程的执行。此时async的异常只会在调用await()时抛出,try-catch可正常捕获:

import kotlinx.coroutines.*

fun log(message: String) {
    println("[${Thread.currentThread()}] $message")
}

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Caught $exception")
    }
    // 使用SupervisorJob创建隔离作用域
    val scope = CoroutineScope(SupervisorJob() + handler)
    val task = scope.async {
        log("throw exception")
        throw RuntimeException("Exception")
    }
    scope.launch {
        try {
            task.await()
        } catch (e: Exception) {
            log("catch exception: ${e.message}")
        }
        delay(1000)
        log("finish await")
    }
    delay(2000)
    scope.cancel()
}

方法2:在同一协程内调用await()并捕获

如果不需要跨协程调用await(),可直接在创建async的协程内执行await(),此时try-catch能直接捕获异常:

import kotlinx.coroutines.*

fun log(message: String) {
    println("[${Thread.currentThread()}] $message")
}

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Caught $exception")
    }
    val scope = CoroutineScope(handler)
    scope.launch {
        val task = async {
            log("throw exception")
            throw RuntimeException("Exception")
        }
        try {
            task.await()
        } catch (e: Exception) {
            log("catch exception: ${e.message}")
        }
        delay(1000)
        log("finish await")
    }
    delay(2000)
}

方法3:等待作用域协程完成触发处理器

若想让CoroutineExceptionHandler捕获异常,需等待自定义scope的协程完成,确保处理器有机会执行:

import kotlinx.coroutines.*

fun log(message: String) {
    println("[${Thread.currentThread()}] $message")
}

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Caught $exception")
    }
    val scope = CoroutineScope(handler)
    val task = scope.async {
        log("throw exception")
        throw RuntimeException("Exception")
    }
    // 等待async协程完成,触发作用域的异常处理器
    task.join()
    delay(2000)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 18:13:12