Home > OS >  Calling a method from an actor's init method
Calling a method from an actor's init method

Time:01-28

I'm trying to convert one of my classes in Swift to an actor. The current init method of my class calls another instance method to do a bunch of initialization work. After converting, here's a simplified version of my actor:

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
}

I get the following error when I try to compile:

Actor-isolated instance method 'initialize()' can not be referenced from a non-isolated context; this is an error in Swift 6

I found that I can replace self.initialize() with:

Task { await self.initialize() }

Is that the best way to do this? Could this cause any race conditions where an external caller can execute a method on my actor before the initialize() method has a chance to run? Seems cumbersome that you are not in the isolated context in the actor's init method. I wasn't able to find any Swift documentation that explained this.

CodePudding user response:

I wasn't able to find any Swift documentation that explained this.

This is explained in the proposal SE-0327, in this section (emphasis mine):

An actor's executor serves as the arbiter for race-free access to the actor's stored properties, analogous to a lock. A task can access an actor's isolated state if it is running on the actor's executor. The process of gaining access to an executor can only be done asynchronously from a task, as blocking a thread to wait for access is against the ethos of Swift Concurrency. This is why invoking a non-async method of an actor instance, from outside of the actor's isolation domain, requires an await to mark the possible suspension. The process of gaining access to an actor's executor will be referred to as "hopping" onto the executor throughout this proposal.

Non-async initializers and all deinitializers of an actor cannot hop to an actor's executor, which would protect its state from concurrent access by other tasks. Without performing a hop, a race between a new task and the code appearing in an init can happen:

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() //            
  • Related