Swift isolated 参数为何没有强制其他参数遵循 Sendable?
结论
isolated BankAccount 会将 charge 函数隔离到传入的 BankAccount actor,让函数体可以直接访问该 actor 的状态。它不会因此将其他参数标记为 Sendable。非 Sendable 值能否传入,取决于调用是否跨越隔离域,以及编译器能否确认传递不会导致并发访问。
Swift 的 region-based isolation 允许编译器在满足安全条件时转移非 Sendable 值。例如,值位于独立区域,传递后也不会再从原隔离域访问。具体诊断会因 Swift 版本、语言模式和并发检查设置而异。题目没有提供这些信息,因此仅凭代码无法判断编译器为何没有报告。可参考 SE-0414:Region-Based Isolation 和 Swift 编译器的跨隔离域诊断说明。
这段代码里的隔离关系
charge 声明了 from bankAccount: isolated BankAccount,因此函数体在该 BankAccount 的 actor 隔离下执行,可以直接访问 withdraw 和 balance。调用处使用 await,因为执行需要切换到对应的 actor 隔离域。
AlternativeBankAccount 是普通 class,没有显式遵循 Sendable。这并不表示每次调用都必须将它声明为 Sendable。编译器可能会根据值所在的隔离区域和后续使用方式,判断是否可以安全转移。isolated 参数不会让 AlternativeBankAccount 变成线程安全类型,也不能保证所有编译设置下都不会出现诊断。
如何判断是否安全
题目中的 bankAccount2 在 @MainActor 方法 random 中创建,之后被捕获进 Task。日志显示这次运行分别打印了 main 线程和编号为 7 的线程,但线程编号无法说明 actor 隔离情况;线程和 actor executor 是两个不同的概念。
如果编译器接受这段代码,应结合所用的 Swift 版本和并发检查模式确认适用的规则。如果 AlternativeBankAccount 是可从多个隔离域共享的可变引用类型,即使没有警告,也不能据此认定共享访问是安全的。将对象放在明确的隔离域中,通常更容易表达预期。
例如,可以将账户建模为 actor:
actor AlternativeBankAccount {
var balance: Double
init(initialDeposit: Double) {
balance = initialDeposit
}
func deposit(amount: Double) {
balance += amount
}
}
struct Charger {
func charge(
amount: Double,
from bankAccount: isolated BankAccount,
to otherAccount: AlternativeBankAccount
) async throws -> (Double, Double) {
try bankAccount.withdraw(amount: amount)
let newBalance = bankAccount.balance
await otherAccount.deposit(amount: amount)
let otherBalance = await otherAccount.balance
return (newBalance, otherBalance)
}
}
actor 引用可以安全地跨隔离域传递。访问另一个 actor 的隔离状态时,需要使用 await。这样,两个账户分别由各自的 actor 保护,但它们之间的操作仍不是一个不可分割的事务。
备注:内容仅供参考。
