1️⃣ 核心结论
在 MainActor + DispatchQueue 混用时,死锁通常源于:
- 主线程被同步阻塞(
DispatchQueue.main.sync {})- async/await 在主线程挂起等待自身执行完成
避免方法:
- 避免在主线程使用同步队列调用(
sync)- 使用
await MainActor.run {}或异步DispatchQueue.main.async- 耗时操作切到后台线程
一句话:主线程永远不要被同步阻塞或等待它自己完成异步任务。
2️⃣ 常见死锁场景
① 同步阻塞主线程
DispatchQueue.main.sync {
Task {
await updateUI()
}
}
-
问题:
- 主线程被
sync阻塞 - Task 在 MainActor(主线程)排队执行 → 永远无法执行
- 主线程被
-
结果 → 死锁
② async/await 自己等待自己
@MainActor
func updateUI() async {
await updateUI() // ❌ 自己等待自己
}
- Task 在主线程排队执行
- await 让 Task 挂起
- 没有其它线程执行 → 死锁
3️⃣ 正确模式
① 使用 await MainActor.run {} 或异步 Task
Task.detached {
// 后台线程执行耗时任务
let data = await fetchData()
await MainActor.run {
// 回主线程更新 UI
label.text = data
}
}
- Task.detached → 后台线程
- MainActor.run → 异步切换回主线程
- 不阻塞主线程 → 避免死锁
② 避免同步 DispatchQueue.main.sync
- 不要用
sync调度到主线程等待结果 - 用异步方式:
DispatchQueue.main.async {
label.text = "Hello"
}
- 或用
await MainActor.run {} - 两者都不会阻塞调用线程
③ 耗时任务切到后台
@MainActor
func updateUI() async {
let result = await Task.detached {
computeHeavyTask()
}.value
label.text = "(result)"
}
- CPU 密集任务不在主线程
- 主线程只负责 UI 更新 → 保持响应流畅
- async/await 保证线程安全,不用锁
④ 小心 TaskGroup 与 MainActor
await withTaskGroup(of: Void.self) { group in
group.addTask {
await Task.sleep(1_000_000_000)
await MainActor.run { print("UI update") }
}
}
- TaskGroup 子任务在后台执行
- UI 更新通过
MainActor.run - 避免在 MainActor 上直接等待 TaskGroup 内子任务完成 → 防止主线程阻塞
4️⃣ 死锁排查思路
-
主线程是否被阻塞?
- DispatchQueue.main.sync
- MainActor.run 的外层同步调用
-
异步任务是否排队自己等待自己?
-
CPU 密集任务是否在主线程执行?
- 长循环或同步 I/O
5️⃣ 面试必背总结
- 不要在主线程使用
DispatchQueue.main.sync,永远用 async 或 await- 耗时任务切后台,UI 更新回主线程
- 避免 async 函数自己等待自身 → 死锁
- MainActor.run 异步切换 + Task.detached / TaskGroup 后台执行 → 安全模式
- 检查 Task 排队顺序和挂起点,确保主线程不会被阻塞