Skip to content

作用域与 Job 树

源:结构化并发核心机制

CoroutineScope 不仅仅是一个上下文容器,它是结构化并发 (Structured Concurrency) 的锚点。

Job 树 (The Job Tree)

协程通过 Job 自动建立父子关系。

mermaid
graph TD
    Scope[Scope (Parent Job)] --> JobA(Job A)
    Scope --> JobB(Job B)
    JobA --> JobA1(Job A-1)
    JobA --> JobA2(Job A-2)

三大定律

  1. 取消传播:Scope 取消 -> Job A/B 取消 -> Job A-1/A-2 取消。
  2. 异常传播:Job A-1 抛出异常 -> Job A 取消 -> Scope 取消 -> Job B 取消。
  3. 生命周期:Scope 只有在 A、B、A-1、A-2 全部执行完毕后,自己才能完成。

致命错误:断链 (Breaking the Structure)

极其危险

launch 时手动传入一个全新的 Job 实例,会切断它与父级作用域的联系。

kotlin
val scope = CoroutineScope(Job())

scope.launch(Job()) { // ❌ 传入了新 Job,导致断链!
    // 1. 父 scope 取消时,这个协程不会取消 (内存泄漏)。
    // 2. 这个协程抛出异常时,父 scope 无法感知。
    delay(1000000)
}

标准作用域

Android Jetpack 提供了与生命周期绑定的作用域:

作用域绑定对象默认调度器取消时机
lifecycleScopeActivity / FragmentDispatchers.Main.immediateonDestroy
viewModelScopeViewModelDispatchers.Main.immediateonCleared
GlobalScope进程Dispatchers.Default永不 (除非进程杀掉)

GlobalScope

严禁在业务逻辑中使用 GlobalScope。它破坏了结构化并发,导致任务无法被取消,是内存泄漏的温床。

自定义作用域模式

如果你需要在非生命周期类(如 Repository 或 Service)中启动协程,请遵循以下标准模式:

kotlin
class DataRepository : AutoCloseable {
    // 1. 使用 SupervisorJob 防止单个子任务失败导致整个 Scope 崩溃
    // 2. 默认使用 IO 调度器
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    fun loadData() {
        scope.launch { /* ... */ }
    }

    // 3. 必须提供关闭/清理方法
    override fun close() {
        scope.cancel() 
    }
}