取消与资源清理 (NonCancellable)
协程的取消是协作式的。当一个协程被取消时,它并不会立即停止,而是会抛出一个 CancellationException。因此,正确处理这个异常并释放资源至关重要。
基础清理:try-finally
这是最标准的清理方式。无论协程是正常结束还是被取消,finally 块中的代码都保证会被执行。
kotlin
val job = launch {
try {
work()
} finally {
// ✅ 这里的代码保证会被执行
closeResources()
println("Resource released")
}
}严重错误:在 finally 中挂起
死锁风险
当协程处于“被取消”状态时,它不能再挂起。如果你在 finally 块中调用了任何挂起函数(如 delay 或 emit),它会立即再次抛出 CancellationException,导致后续的清理代码无法执行!
kotlin
// ❌ 错误做法
finally {
delay(1000) // 再次抛出异常,这里会直接崩溃或中断
println("This will NOT be printed")
}解决方案:withContext(NonCancellable)
如果你必须在 finally 块中调用挂起函数(例如:异步关闭数据库、发送网络请求通知服务器),你必须显式地打破“取消状态”。
NonCancellable 是一个特殊的单例上下文元素,它用于创建一个即使父协程被取消,我也依然活跃的特殊环境。
kotlin
val job = launch {
try {
work()
} finally {
// ✅ 正确做法:切换到不可取消的上下文
withContext(NonCancellable) {
delay(1000) // 现在可以安心挂起了
println("Cleanup done")
}
}
}常见的关闭模式:use
对于实现了 Closeable 接口的资源,Kotlin 标准库提供了 use 扩展函数,它内部自动封装了 try-finally 逻辑,非常推荐。
kotlin
// 读取文件流
FileInputStream(file).use { stream ->
// 做一些挂起操作
// 离开这个块时,stream.close() 会被自动调用
}Android 生命周期关联
viewModelScope: 当 ViewModel 清除时自动取消。lifecycleScope: 当 Activity/Fragment 销毁时自动取消。
在这些作用域中启动的协程,必须时刻警惕取消的发生,并使用上述技术确保资源不泄漏。