Kotlin协程异常处理
外观
Kotlin协程异常处理[编辑 | 编辑源代码]
概述[编辑 | 编辑源代码]
协程异常处理是结构化并发中的核心机制,用于管理协程执行过程中出现的错误。与线程不同,协程通过父子层级关系传播异常,提供更可控的错误处理方式。Kotlin使用`CoroutineExceptionHandler`和`try/catch`等机制实现多层级的异常管理。
关键特性:
- 自动传播:未捕获的异常会取消父协程及同级协程
- 结构化处理:通过Job层级实现异常传播路径控制
- 多处理策略:支持集中处理(`CoroutineExceptionHandler`)和局部处理(`try/catch`)
异常传播机制[编辑 | 编辑源代码]
层级传播原理[编辑 | 编辑源代码]
异常传播遵循以下规则: 1. 子协程抛出未捕获异常时,首先取消自己的所有子协程 2. 异常向上传递给父协程 3. 父协程取消所有其他子协程 4. 继续向上传播直到根协程或捕获点
传播类型对比[编辑 | 编辑源代码]
启动方式 | 自动传播 | 处理方式 |
---|---|---|
是 | 可通过`CoroutineExceptionHandler`捕获 | ||
否 | 需通过`await()`显式处理 |
基础处理方式[编辑 | 编辑源代码]
try/catch块[编辑 | 编辑源代码]
最直接的异常处理方式,适用于同步代码和挂起函数:
fun main() = runBlocking {
try {
launch {
throw ArithmeticException("计算错误")
}
} catch (e: Exception) {
println("捕获到异常: ${e.message}") // 不会执行!
}
delay(1000) // 给协程时间执行
}
页面模块:Message box/ambox.css没有内容。
直接使用`try/catch`无法捕获`launch`内部的异常,因为协程是异步执行的 |
正确用法:
suspend fun riskyOperation() {
throw IOException("文件操作失败")
}
fun main() = runBlocking {
try {
riskyOperation() // 在挂起函数中使用
} catch (e: IOException) {
println("处理IO异常: ${e.message}")
}
}
CoroutineExceptionHandler[编辑 | 编辑源代码]
全局异常处理器,适用于根协程:
val handler = CoroutineExceptionHandler { _, exception ->
println("全局处理: ${exception.javaClass.simpleName} - ${exception.message}")
}
fun main() = runBlocking {
val scope = CoroutineScope(Job() + handler)
scope.launch {
throw NullPointerException("空指针异常")
}.join()
}
输出:
全局处理: NullPointerException - 空指针异常
高级处理策略[编辑 | 编辑源代码]
SupervisorJob模式[编辑 | 编辑源代码]
使用`SupervisorJob`可阻止异常传播到同级协程:
实现示例:
fun main() = runBlocking {
val supervisor = SupervisorJob()
val scope = CoroutineScope(coroutineContext + supervisor)
scope.launch {
delay(100)
throw RuntimeException("失败协程")
}
scope.launch {
repeat(5) { i ->
println("健康协程 $i")
delay(200)
}
}.join()
}
输出:
健康协程 0 健康协程 1 健康协程 2 健康协程 3 健康协程 4
async/await异常处理[编辑 | 编辑源代码]
`async`协程需通过`await()`捕获异常:
fun main() = runBlocking {
val deferred = async {
throw CustomException("异步操作失败")
}
try {
deferred.await()
} catch (e: CustomException) {
println("捕获异步异常: ${e.message}")
}
}
复合场景处理[编辑 | 编辑源代码]
多协程异常合并[编辑 | 编辑源代码]
当多个子协程抛出异常时,Kotlin会保留第一个异常并通过`suppressed`属性附加后续异常:
fun main() {
val handler = CoroutineExceptionHandler { _, e ->
println("主异常: ${e.message}")
e.suppressed.forEach {
println("附加异常: ${it.message}")
}
}
runBlocking {
val scope = CoroutineScope(handler)
scope.launch {
launch { throw IOException("IO错误") }
launch { throw IllegalArgumentException("参数错误") }
}.join()
}
}
资源清理保证[编辑 | 编辑源代码]
使用`try/finally`或`use`确保资源释放:
suspend fun writeToFile() {
val resource = acquireResource()
try {
resource.use {
delay(500) // 模拟操作
throw IOException("写入失败")
}
} finally {
println("清理临时文件")
}
}
最佳实践[编辑 | 编辑源代码]
1. 层级设计原则:
* 对不可恢复错误使用全局处理器 * 对业务异常使用局部`try/catch`
2. 恢复策略:
* 实现重试机制时结合`retryWhen`:
suspend fun <T> retry(
times: Int,
delay: Long,
block: suspend () -> T
): T {
var lastEx: Exception? = null
repeat(times) { attempt ->
try {
return block()
} catch (e: Exception) {
lastEx = e
delay(delay)
}
}
throw lastEx ?: RuntimeException("未知错误")
}
3. 监控集成:
* 在`CoroutineExceptionHandler`中添加日志和监控上报
常见问题[编辑 | 编辑源代码]
数学表达[编辑 | 编辑源代码]
异常传播的取消范围可以用集合表示: 设协程树为,其中:
- 为抛出异常的节点
- 受影响节点集
总结[编辑 | 编辑源代码]
Kotlin协程异常处理的核心要点: 1. 理解结构化并发的传播机制 2. 根据场景选择局部处理或全局处理 3. 重要资源必须保证清理 4. 合理使用监督作用域(SupervisorJob)隔离异常
通过结合`try/catch`、`CoroutineExceptionHandler`和协程构建器策略,可以构建健壮的异步错误处理系统。