完整教程:Kotlin结构化并发:彻底改变异步编程的设计哲学
目录
引言:为什么我们需要结构化并发?
在传统的异步编程中,我们经常面临这样的问题:启动的协程丢失了、资源泄漏、异常被静默吞噬、取消操作复杂难控。这些问题在大型应用中尤为突出,往往导致难以追踪的bug和性能问题。
Kotlin的结构化并发(Structured Concurrency)正是为了解决这些问题而生。它不是简单的语法糖,而是一种全新的编程范式,通过严格的父子关系管理协程生命周期,让异步代码变得可预测、可维护、可测试。
一、什么是结构化并发?
1.1 核心概念
结构化并发是一种编程范式,它要求所有协程必须在特定的作用域内启动,并且父协程负责管理所有子协程的生命周期。这种设计确保了:
- 没有孤立的协程:每个协程都有明确的父协程
- 自动取消传播:父协程取消时,所有子协程自动取消
- 异常传播可控:子协程异常可以按需传播或隔离
1.2 与传统并发模型的对比
// ❌ 传统并发:容易创建"孤儿"协程
fun dangerousAsync() {
// 这个协程没有父作用域,无法被取消,可能导致泄漏
GlobalScope.launch {
delay(10000)
println("我可能永远不会执行完,或者泄漏内存")
}
}
// ✅ 结构化并发:协程与作用域绑定
fun safeAsync() = runBlocking {
// 这个协程在runBlocking作用域内,会被正确管理
launch {
delay(1000)
println("我将在父作用域结束时被取消")
}
}
二、结构化并发的核心机制
2.1 协程作用域的层次结构
在结构化并发中,协程以树状结构组织,每个协程都知道自己的父协程:
suspend fun hierarchicalStructure() = coroutineScope {
println("父协程开始")
launch {
// 子协程1
println("子协程1开始")
launch {
// 孙子协程
println("孙子协程执行")
delay(500)
}
delay(1000)
println("子协程1结束")
}
launch {
// 子协程2
println("子协程2开始")
delay(1500)
println("子协程2结束")
}
println("父协程等待所有子协程完成")
// 父协程会自动等待所有子协程完成
}
运行输出:
父协程开始
子协程1开始
子协程2开始
父协程等待所有子协程完成
孙子协程执行
子协程1结束
子协程2结束
2.2 自动取消传播:最强大的特性
结构化并发最强大的特性之一是取消的自动传播:
fun cancellationPropagation() = runBlocking {
val parentJob = launch {
println("父协程开始")
launch {
try {
repeat(1000) {
i ->
println("子协程1: 任务 $i")
delay(100)
}
} finally {
// 即使被取消,finally块也会执行
println("子协程1: 清理资源")
}
}
launch {
repeat(10) {
i ->
println("子协程2: 任务 $i")
delay(100)
}
println("子协程2: 故意抛出异常")
throw RuntimeException("子协程2失败!")
}
}
// 等待父协程完成(或失败)
parentJob.join()
}
在这个例子中,当子协程2抛出异常时:
- 子协程2自身被取消
- 父协程被取消
- 子协程1也被取消
- 所有finally块都会执行,确保资源清理
2.3 SupervisorJob:隔离异常的守护者
有时我们不想让一个子协程的失败影响其他兄弟协程,这时可以使用SupervisorJob:
fun supervisorExample() = runBlocking {
supervisorScope {
println("Supervisor作用域开始")
val child1 = launch {
repeat(5) {
i ->
println("子协程1: 工作 $i")
delay(100)
}
// 这个异常不会影响其他协程
throw RuntimeException("子协程1失败!")
}
val child2 = launch {
repeat(10) {
i ->
println("子协程2: 继续工作 $i")
delay(
浙公网安备 33010602011771号