2-16.【Concurrency】非 actor 上下文访问 actor 属性需要使用 await 的原理是什么?

4 阅读2分钟

1️⃣ 核心结论

非 Actor 上下文访问 Actor 属性必须使用 await,是因为跨 Actor 调用会被封装为异步任务,放入 Actor 的 executor 队列排队执行,从而保证数据隔离和线程安全。

换句话说:await = 等待 Actor 内部串行任务完成


2️⃣ 原理:Actor 的隔离机制

  1. 每个 Actor 拥有自己的 executor(串行队列)。
  2. Actor 内部的状态只能在这个 executor 上安全访问。
  3. 同时只能有一个任务在 Actor 内部执行。
Actor (executor)
 ├─ Task 1
 ├─ Task 2 queued
 └─ Task 3 queued
  • 任何跨 Actor 的访问都需要经过 executor 排队。

3️⃣ 编译器约束

  • 如果你在 非 Actor 上下文直接访问属性,编译器报错:
actor BankAccount {
    var balance: Int = 0
}

let account = BankAccount()
account.balance += 10 // ❌ 编译错误

原因:

  • 当前上下文不在 BankAccount 的 executor
  • 无法保证线程安全
  • 编译器要求 跨 Actor 访问必须显式 await,以触发异步排队。

4️⃣ runtime 工作机制

当你写:

await account.deposit(100)

发生了什么:

  1. 编译器将调用封装成 任务(continuation)
  2. 这个任务被放入 accountexecutor 队列
  3. executor 串行执行 Actor 内部代码
  4. await 等待任务完成,然后返回结果给调用者

所以 await 本质上是:

  • 一个“切换到 Actor 的串行队列执行任务,并等待完成”的操作。

非 await 调用无法保证安全

  • 如果没有 await,访问可能在不同线程同时进行
  • Actor 内部属性可能被并发修改 → 数据竞争
  • Swift 编译器通过类型系统和 async/await 强制你遵守

5️⃣ 作用总结

功能解释
await等待 Actor 内部任务执行完成
executor串行化 Actor 内部访问
async 消息跨 Actor 访问封装为任务
数据隔离保证同一时间只有一个任务访问属性

6️⃣ 类比理解

  • Actor = 单线程服务 + 异步队列
  • 非 Actor 调用 = 发消息请求
  • await = 等待消息处理完成返回结果

像发快递:

  • 你不能直接拿仓库里的东西(非 Actor 上下文访问)
  • 只能发快递(async/await),等待仓库处理完成才拿到

7️⃣ 面试必背总结

在 Swift 中,非 Actor 上下文访问 Actor 属性必须使用 await,因为每个 Actor 内部有自己的 executor,属性访问必须在该 executor 上串行执行;await 会把调用封装成异步任务,放入 Actor 队列执行并等待完成,从而保证数据隔离和线程安全。