从 actor 的 init 方法中调用方法

K M*_*hta 2 concurrency actor swift

我正在尝试将 Swift 中的一个类转换为actor. 我的类的当前init方法调用另一个实例方法来完成一系列初始化工作。转换后,这是我的演员的简化版本:

actor MyClass {
    private let name: String

    init(name: String) {
        self.name = name

        self.initialize()  // Error on this line
    }

    private func initialize() {
        // Do some work
    }

    func login() {
        self.initialize()
        // Do some work
    }

    // Bunch of other methods
}
Run Code Online (Sandbox Code Playgroud)

当我尝试编译时出现以下错误:

无法从非隔离上下文引用 Actor 隔离的实例方法“initialize()”;这是 Swift 6 中的一个错误

我发现我可以替换self.initialize()为:

Task { await self.initialize() }
Run Code Online (Sandbox Code Playgroud)

这是最好的方法吗?这是否会导致任何竞争条件,外部调用者可以在该initialize()方法有机会运行之前在我的参与者上执行该方法?看起来很麻烦,因为您不在参与者的 init 方法中的孤立上下文中。我找不到任何解释这一点的 Swift 文档。

Swe*_*per 5

我找不到任何解释这一点的 Swift 文档。

这在提案SE-0327 的本节中进行了解释(强调我的):

Actor 的执行者充当对 Actor 存储的属性进行无竞争访问的仲裁者,类似于锁。如果任务在参与者的执行器上运行,则该任务可以访问参与者的隔离状态。获取执行器访问权限的过程只能通过任务异步完成,因为阻塞线程等待访问违背了 Swift 并发的精神。这就是为什么从参与者的隔离域外部调用参与者实例的非异步方法需要await标记可能的暂停。在本提案中,访问执行者的执行者的过程将被称为“跳到”执行者。

Actor 的非异步初始化程序和所有反初始化程序都无法跳转到 Actor 的执行程序,这将保护其状态免受其他任务的并发访问。如果不执行跳跃,新任务和出现在 中的代码之间init可能会发生竞争:

actor Clicker {
  var count: Int
  func click() { self.count += 1 }

  init(bad: Void) {
    self.count = 0
    // no actor hop happens, because non-async init.

    Task { await self.click() }

    self.click() //  this mutation races with the task!

    print(self.count) //  Can print 1 or 2!
  }
}
Run Code Online (Sandbox Code Playgroud)

为了防止上述竞争,Swift 5.5对非异步初始化器中init(bad:)可以执行的操作施加了限制。self特别是,self在闭包捕获中进行转义,或者在方法调用中(隐式)传递给click,会触发警告,表明此类使用self将在 Swift 6 中出现错误。

然后他们继续给出了另一个应该被编译器接受的例子,并建议放宽这些限制。但无论哪种情况,它都不适用于您的代码。如果您想了解为什么它仍然无法在较新版本的 Swift 中编译的详细信息,请参阅本节。

所以解决方案是,正如第一句话所说,将其标记initasync。这样初始化器就可以在参与者的执行器上有效地运行。调用者await在初始化时会使用“跳”进去。