给协程找一个「爹」——谁生谁管、谁死谁死,父子连锁永不泄漏
// 没有作用域:到处乱 launch
fun onUserLogin(userId: String) {
GlobalScope.launch { // ① 启动一个协程
val data = fetchUser(userId)
cacheUser(data)
}
GlobalScope.launch { // ② 又启动一个
val ads = fetchAds(userId)
showAds(ads)
}
// ③ 再来一个
GlobalScope.launch {
updateLastLogin(userId)
}
}
场景1:用户快速退出登录→ onUserLogin返回了 → 但 3 个协程还在跑!→ 用户都走了,你还在更新他的缓存?浪费!
场景2:① 抛异常了→ ② 和 ③ 完全不知道 → ② 展示的广告基于旧数据,不一致
场景3:页面退出了→ 协程还在后台跑,没人管它们死活 → 内存泄漏
这就是没爹没妈的孩子——没人管,死活无人问。
fun onUserLogin(userId: String) = coroutineScope { // ← 创建一个作用域
launch { fetchUser(userId) } // 子协程 1
launch { fetchAds(userId) } // 子协程 2
launch { updateLastLogin(userId) } // 子协程 3
}
// ← 所有子协程都结束了,这行才执行
coroutineScope {
launch {
delay(1000)
println("A 完成")
}
launch {
delay(2000)
println("B 完成")
}
println("子协程都结束了我才打印这行")
}
// 输出:
// A 完成
// B 完成
// 子协程都结束了我才打印这行
父协程在 coroutineScope结束前会挂起,等所有子协程跑完。
coroutineScope {
launch {
delay(500)
throw RuntimeException("我挂了!") // A 出异常
}
launch {
delay(1000)
println("这行不会打印") // B 被取消
}
}
// A 抛异常 → 父协程取消 → B 被取消 → 异常抛给调用者
coroutineScope {
launch {
delay(500)
println("A 跑完了")
}
delay(100) // 等 100ms
throw RuntimeException("爹挂了") // 在子协程跑完前爹就挂了
}
// A 还没跑完就被取消
| 铁律 | 触发条件 | 后果 |
|---|---|---|
| 铁律一 | 父协程进入 coroutineScope | 父协程挂起,等所有子协程完成才继续 |
| 铁律二 | 任意子协程抛未捕获异常 | 父取消 → 所有兄弟姐妹被取消 → 异常向上传播 |
| 铁律三 | 父协程被取消或抛异常 | 所有子协程被取消 → 一个不留 |
suspend fun loadData() = coroutineScope {
val user = async { fetchUser() } // 并发
val ads = async { fetchAds() } // 并发
UserPage(user.await(), ads.await()) // 两个都拿到才返回
}
suspend fun loadSafe() = supervisorScope {
launch { fetchUser() } // 挂了
launch { fetchAds() } // 不受影响,继续跑
launch { updateUI() } // 不受影响
}
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch { // ViewModel 销毁时自动取消
val data = repository.fetch()
_state.value = data
}
}
}
无作用域:漫天野孩子,挂了跑了没人管,泄漏、异常、数据不一致。有作用域:每个协程都有爹,父子连锁——爹等孩子、孩子死全家死、爹死全家死、生命周期自动绑定。这就叫「结构化并发」= Structured Concurrency。