2

I'm in the process of converting my code to using Swift concurrency and I'm running into an issue with Actor which I don't know how to fix it correctly.

Here is a simple actor:

actor MyActor {
    private var count = 0
    
    func increase() {
        count += 1
    }
}

In other places where I need to update the actor, I have to call its functions in concurrency context:

Task {
    await myActor.increase()
}

That's good. But what I don't understand is if the actor return the increase function as a closure like this:

actor MyActor {
    private var count = 0
    
    func increase() -> () -> Void {
        {
            print("START")
            self.count += 1
            print("END")
        }
    }
}

In other places, I can get a reference to the returned closure and call it freely in non-concurrency context:

class ViewController: UIViewController {
    let actor = MyActor()

    var increase: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        Task {
            increase = await actor.increase()
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            let increase = self.increase
            
            DispatchQueue.concurrentPerform(iterations: 100) { _ in
                increase?()
            }
        }
    }
}

The above code print this to the output:

START
START
START
START
START
END
END
START
START
...

I'm not sure if I understand or use Actor correctly. Actor protects its state from data races, but in this case, it does not prevent that. Is it correct behavior? Is there a way to fix it?

Arnol
  • 2,107
  • 2
  • 17
  • 19
  • Do not use Swift Concurrency (Task and actor and async / await) and DispatchQueue together! They are opposites – matt Dec 25 '21 at 04:14
  • 2
    I strongly suspect this is a compiler bug (or more specifically, a current deficiency). See https://bugs.swift.org/browse/SR-15235 for some changes in 5.6 that may improve this. Also turn on `-warn-concurrency` and see if this generates a warning. There is ongoing work to fully enforce every case. You certainly should not do this. As you say, it's broken (but it's also a pretty big red-flag to be capturing an actor in a closure; don't do that). I expect the compiler will be better at catching these mistakes in the future. – Rob Napier Dec 25 '21 at 04:37
  • A subtle duplicate of https://stackoverflow.com/questions/68864640/why-is-it-legal-to-mutate-an-actors-nonsendable-property perhaps? – matt Dec 25 '21 at 11:17
  • @RobNapier Yes, the compiler generates a warning if I turn on that option. I ended up removing the closure. Thanks! – Arnol Dec 26 '21 at 02:52

0 Answers0