第三部分:函数式编程与并发
第 13 章:协程进阶
13.1 协程的取消与异常处理
1. 协程的取消
在 Kotlin 协程中,取消(Cancellation)是一种协作机制,用于优雅地终止正在运行的协程。以下是关键点:
取消协程的方式
通过调用Job.cancel()方法取消协程:val job = launch { repeat(1000) { i -> println("Job: I'm sleeping $i...") delay(500L) } } delay(1300L) job.cancel() // 取消协程检查协程是否活跃
协程内部需通过isActive或ensureActive()检查取消状态:launch { while (isActive) { // 检查协程是否被取消 println("Working...") delay(100) } }释放资源
使用try {...} finally {...}或suspendCancellableCoroutine确保资源释放:job.cancel() job.join() // 等待协程完成取消
2. 异常处理
协程中的异常传播遵循结构化并发规则,需通过 CoroutineExceptionHandler 或 try-catch 捕获。
异常传播机制
- 子协程异常会取消父协程及同级协程(除非使用
SupervisorJob)。 - 通过
CoroutineExceptionHandler全局捕获异常:val handler = CoroutineExceptionHandler { _, exception -> println("Caught $exception") } val scope = CoroutineScope(Job() + handler) scope.launch { throw AssertionError("Test Exception") }
- 子协程异常会取消父协程及同级协程(除非使用
SupervisorJob
使用SupervisorJob隔离子协程的异常影响:val supervisor = SupervisorJob() with(CoroutineScope(coroutineContext + supervisor)) { launch { // 子协程1失败不会影响子协程2 delay(100) throw RuntimeException("Failed") } launch { delay(200) println("Still running") } }
3. 超时控制
通过 withTimeout 或 withTimeoutOrNull 实现超时取消:
// 超时抛出 TimeoutCancellationException
try {
withTimeout(1000) {
delay(2000)
}
} catch (e: TimeoutCancellationException) {
println("Timed out!")
}
// 超时返回 null
val result = withTimeoutOrNull(1000) {
delay(2000)
"Done"
}
println(result) // 输出 null
4. 最佳实践
- 避免阻塞操作:在协程中使用
suspend函数替代Thread.sleep()。 - 清理逻辑:通过
invokeOnCompletion注册取消回调。 - 测试支持:使用
runTest(kotlinx-coroutines-test)模拟协程取消场景。
代码示例
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
ensureActive() // 检查取消状态
println("Processing $i")
delay(100)
}
} finally {
println("Cleanup resources")
}
}
delay(250)
job.cancelAndJoin() // 取消并等待完成
}
注意:协程取消是协作式的,需在协程内部主动检查取消状态或调用可取消的挂起函数(如
delay、yield)。
