Skip to content

取消与资源清理 (NonCancellable)

源:Kotlin 协程取消与超时指南

协程的取消是协作式的。当一个协程被取消时,它并不会立即停止,而是会抛出一个 CancellationException。因此,正确处理这个异常并释放资源至关重要。

基础清理:try-finally

这是最标准的清理方式。无论协程是正常结束还是被取消,finally 块中的代码都保证会被执行。

kotlin
val job = launch {
    try {
        work()
    } finally {
        // ✅ 这里的代码保证会被执行
        closeResources()
        println("Resource released")
    }
}

严重错误:在 finally 中挂起

死锁风险

当协程处于“被取消”状态时,它不能再挂起。如果你在 finally 块中调用了任何挂起函数(如 delayemit),它会立即再次抛出 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 销毁时自动取消。

在这些作用域中启动的协程,必须时刻警惕取消的发生,并使用上述技术确保资源不泄漏。