Kotlin协程异常传播机制
一、异常传播机制详解
1. 结构化并发与异常传播规则
-
默认传播行为:子协程的未捕获异常会向上传播至父协程,触发父协程取消并递归取消所有子协程(即“连坐制”)。
viewModelScope.launch { launch { // 子协程1 throw RuntimeException("子协程崩溃") // 触发父协程取消 } launch { // 子协程2(会被连带取消) delay(1000) } } -
coroutineScope与supervisorScope的区别:-
coroutineScope:子协程异常导致整个作用域取消(默认行为)。 -
supervisorScope:子协程异常独立处理,不影响其他子协程。
-
2. 异常传播路径
| 构建器 | 异常传播方式 | 典型场景 |
|---|---|---|
|
| 立即抛出异常,向上层传播 | UI事件处理 |
|
| 延迟到 | 并行数据加载 |
|
| 通过 | 生产者-消费者模型 |
3. 异常终止链
子协程异常 → 父协程取消 → 兄弟协程取消 → 根协程终止 → 全局异常处理器
-
取消传播:通过
Job.cancel()触发协程取消,但CancellationException会被自动吞没。
二、异常处理核心策略
1. 全局异常捕获(兜底方案)
-
CoroutineExceptionHandler:捕获未处理异常,防止应用崩溃。val handler = CoroutineExceptionHandler { _, ex -> FirebaseCrashlytics.getInstance().recordException(ex) } viewModelScope.launch(handler) { /* ... */ } -
适用场景:日志记录、崩溃上报。
2. 异常隔离(责任划分)
-
SupervisorJob:隔离子协程异常,避免“一损俱损”。val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) scope.launch { /* 任务A */ } // 异常不影响其他任务 scope.launch { /* 任务B */ } -
Android实践:
viewModelScope默认使用SupervisorJob。
3. 局部异常处理(精准控制)
-
try-catch:捕获特定异常,避免全局吞没。viewModelScope.launch { try { val data = withContext(Dispatchers.IO) { fetchData() } } catch (e: IOException) { showErrorUI(e) } } -
async.await():必须包裹try-catch,否则异常可能被静默忽略。
4. 异常传播控制
-
coroutineScope:强制子协程异常传播至父协程。 -
supervisorScope:阻断异常传播,仅处理自身异常。
三、最佳实践与避坑指南
1. 作用域管理
-
避免
GlobalScope:使用结构化作用域(如viewModelScope、lifecycleScope)自动管理生命周期,防止内存泄漏。 -
自定义作用域:业务层通过
coroutineScope或supervisorScope控制任务边界。
2. 异常分层处理
| 层级 | 策略 | 示例 |
|---|---|---|
| UI层 | 捕获异常并展示友好提示 |
|
| 领域层 | 转换异常类型(如网络异常→业务错误) |
|
| 数据层 | 处理原始异常(如Socket超时) | 重试策略、降级数据 |
3. 调试与监控
-
协程调试:启用
-Dkotlinx.coroutines.Debug参数,获取完整协程堆栈。
-
虚拟机设置
-
打开测试配置(Edit Configurations)
-
在 VM options 中添加
-Dkotlinx.coroutines.Debug=on(未成)
-
-
异常聚合:通过
SuppressedExceptions分析多异常关联。
4. 性能优化
-
避免热路径
try-catch:高频代码块减少异常捕获(如循环内)。 -
合理使用
SupervisorJob:仅在需要隔离的场景使用,避免过度设计。
5. 生命周期绑定
-
Android示例:
class MyViewModel : ViewModel() { fun loadData() { viewModelScope.launch { try { val data = repository.fetchData() _uiState.value = data } catch (e: CancellationException) { // 忽略取消异常 } catch (e: Exception) { _uiState.value = ErrorState(e) } } } }
四、典型场景解决方案
场景1:并行请求中部分失败
viewModelScope.launch {
val deferred1 = async { fetchDataA() }
val deferred2 = async { fetchDataB() }
try {
val result = deferred1.await() to deferred2.await()
} catch (e: Exception) {
// 处理任一请求失败
}
}
场景2:UI操作与后台任务解耦
fun onButtonClick() {
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
repository.performHeavyTask()
}
updateUI(result)
}
}
场景3:全局崩溃防护
// Application初始化
val globalHandler = CoroutineExceptionHandler { _, ex ->
Crashlytics.logException(ex)
}
GlobalScope.launch(globalHandler) { /* 后台任务 */ }
五、进阶设计模式
1. 重试机制
suspend fun <T> retry(
times: Int = 3,
initialDelay: Long = 1000,
block: suspend () -> T
): T {
repeat(times) { attempt ->
try {
return block()
} catch (e: Exception) {
if (attempt == times - 1) throw e
delay(initialDelay * (2L pow attempt))
}
}
throw IllegalStateException("Retry failed")
}
2. 错误类型标准化
sealed class AppError(val message: String) : Exception(message) {
data class NetworkError(val code: Int) : AppError("网络异常")
data class DatabaseError(val query: String) : AppError("查询失败")
object AuthExpired : AppError("登录过期")
}
3. 异常恢复策略
suspend fun loadDataWithFallback() {
try {
fetchData()
} catch (e: NetworkError) {
loadCachedData() // 降级策略
} catch (e: DatabaseError) {
fallbackToDefault() // 默认值
}
}
总结
Kotlin协程的异常传播机制通过结构化并发和责任隔离实现高效错误管理。最佳实践包括:
-
作用域规范化:使用
viewModelScope/lifecycleScope。 -
分层处理:UI层捕获展示,数据层处理原始异常。
-
隔离策略:
SupervisorJob隔离独立任务。 -
防御性编程:
try-catch包裹async.await(),避免静默失败。
通过合理设计异常边界和恢复策略,可显著提升应用稳定性和可维护性。
参考
543

被折叠的 条评论
为什么被折叠?



