2-5.【Concurrency】Task 和 DetachedTask 有什么区别?使用场景各是什么?

19 阅读3分钟

一句话总览

Task {} = 有父任务、会继承上下文的结构化并发
Task.detached {} = 没有父任务、完全独立的非结构化并发


1️⃣ 最重要的区别(先看这个)

维度Task {}Task.detached {}
是否有父 Task✅ 有❌ 没有
继承取消状态
继承优先级
继承 Task-local
继承 actor / executor
结构化并发
推荐使用⭐⭐⭐⭐⭐⭐⭐(谨慎)

99% 的情况下,你应该用 Task {}


2️⃣ Task {} 到底是什么?

定义

Task {
    await doSomething()
}

这是一个 child task(子任务)

它会继承什么?

假设你在这里:

@MainActor
func onTap() {
    Task {
        await loadData()
    }
}

这个 Task 会继承:

  • 当前 Task 的取消状态
  • 当前 优先级
  • 当前 Task-local 值
  • 当前 actor(MainActor)

所以:

loadData() // 默认仍在 MainActor

除非你 await 到别的 executor。


为什么这很重要?

因为它保证了:

  • 生命周期清晰
  • 取消可以向下传播
  • 行为可预测

👉 这就是“结构化并发”


3️⃣ Task.detached {} 是什么?

定义

Task.detached {
    await doSomething()
}

这是一个 完全独立的任务

特点(非常关键):

  • ❌ 不属于当前 Task
  • ❌ 不继承 MainActor
  • ❌ 不继承优先级
  • ❌ 不继承取消
  • ❌ 不继承 Task-local

相当于:

“在并发世界里新开了一个孤儿线程(逻辑上)”


4️⃣ 一个最容易踩坑的例子 ⚠️

@MainActor
func onTap() {
    Task.detached {
        updateUI() // ❌ 运行期错误
    }
}

原因:

  • detached task 不在 MainActor
  • 却访问了 MainActor 隔离的状态

必须显式切回:

Task.detached {
    await MainActor.run {
        updateUI()
    }
}

5️⃣ 取消(cancellation)语义差异

Task {}

let task = Task {
    try await longWork()
}

task.cancel()
  • 子任务会被标记为 cancelled
  • await 点会抛 CancellationError
  • 和父任务强关联

Task.detached {}

let task = Task.detached {
    try await longWork()
}
  • 不会被父任务取消
  • 必须手动管理生命周期
  • 很容易“偷偷活很久”

6️⃣ 什么时候该用 Task {}?(几乎所有时候)

✅ UI 事件响应

Button("Load") {
    Task {
        await viewModel.load()
    }
}

✅ actor 内部并发工作

actor Cache {
    func refresh() {
        Task {
            await self.loadFromDisk()
        }
    }
}

✅ 需要自动取消、优先级继承


7️⃣ 什么时候才该用 Task.detached {}

⚠️ 使用条件(缺一不可)

真的需要:

  1. 不继承任何上下文
  2. 生命周期独立于调用者
  3. 手动处理取消 / actor hop

合理场景 1️⃣:全局后台维护任务

Task.detached(priority: .background) {
    await cleanTempFiles()
}
  • 不该被 UI 取消
  • 明确是系统级任务

合理场景 2️⃣:跨 actor / 跨 subsystem 的桥接

Task.detached {
    let data = await fetch()
    await logger.log(data)
}

合理场景 3️⃣:底层库内部(而不是 App 代码)

Swift 官方建议:

Task.detached 主要用于框架 / runtime 层


8️⃣ 官方态度(很明确)

Swift Concurrency 的设计哲学是:

默认结构化并发,尽量消灭 detached task

你可以理解成:

  • Task {} = safe default
  • Task.detached {} = unsafe escape hatch

9️⃣ 快速记忆口诀 🧠

要继承上下文,用 Task
要彻底断亲,用 Task.detached
不确定?别 detached


10️⃣ 最后一段总结(面试版)

Task {} 创建的是结构化的子任务,会继承当前任务的取消、优先级、actor 和 task-local,适合绝大多数并发场景;
Task.detached {} 创建的是非结构化任务,不继承任何上下文,生命周期完全独立,适合极少数系统级或框架级后台工作,普通业务代码应尽量避免。