0

Subclasses inherit the @MainActor attribute which ensures synchronised access on the main thread to all its stored. But what if I don't want this "exclusive access" functionality. Take the following example:

class GameScene: SKScene {
    var imagesToUpload = ["cat", "mouse", "dog"]
    var lastUploaded = ""
    
    func uploadAllImages() async {
        await withTaskGroup(of: Void.self) { group in
            for name in imagesToUpload {
                group.addTask {
                    await self.uploadImage(of: name)
                    self.lastUploaded = name  // << Error: Main actor-isolated property 'lastUploaded' can not be mutated from a Sendable closure
                } 
            }
        }
    }
    
    func uploadImage(of: String) async {
        try! await Task.sleep(for: .seconds(1))
    }
}

SKScene inherits from UIResponder which is declared using @MainActor. From what I understand of this error, a Sendable closure is a closure which captures values that are all Sendable, i.e., safe to access from multiple threads. But in this case, I don't care about multiple threads changing the value of this variable at the same time. To fix this issue in my testing, I had to do this:

group.addTask { @MainActor in
    await self.uploadImage(of: name)
    self.lastUploaded = name
} 

But here is my worry about doing this: If @MainActor closures run on the Main Thread, won't that freeze up the UI due to the upload? In addition to that, it also defeats the purpose of using a TaskGroup as all of these uploads will get queued up on the Main Thread, which will basically make them happen sequentially due to the "await" keyword.

So my question is, how do I asynchronously modify properties that inherit the @MainActor attribute? Is there a way to turn this exclusive/synchronised access off?

Edit: I only want to know if it's possible to remove the inherited @MainActor declaration on my property. Perhaps a way to declare it as nonisolated.

rayaantaneja
  • 1,182
  • 7
  • 18
  • For reference for future readers, this is a refinement (not a duplicate, though) of a prior question: https://stackoverflow.com/questions/75780165/why-am-i-getting-an-error-about-an-actor-isolated-property-on-a-class-while-usin – Rob Mar 20 '23 at 06:08
  • Are you actually seeing it freeze? Or are you just concerned that it might? (The `await` of an upload should not block the main actor, and if it does, there is something else going on.) – Rob Mar 20 '23 at 06:13

1 Answers1

3

You ask:

If @MainActor closures run on the Main Thread, won't that freeze up the UI due to the upload?

No, when you reach the await, the main actor is not blocked, but rather the task suspends and the main actor is free to switch to other tasks while the upload is underway. The await will not block the main actor. (This is in contrast to traditional, blocking, wait API.)

You go on to ask:

Is there a way to turn this exclusive/synchronised access off?

You are writing multithreaded code, and Swift concurrency has these mechanisms to ensure that your code is thread-safe. I would strongly encourage you to embrace these patterns (as confusing as all of these new warnings might seem when you first encounter them).

But, in answer to your question, you theoretically can abandon Swift concurrency and write code that uses legacy API, with no compile-time checks for thread-safety. But I would advise against it. Once you master Swift concurrency, actors, etc., you will start to really appreciate it and will never want to go back to brittle, legacy patterns.


By the way, given your various questions about Swift concurrency and threads, you might find WWDC 2021 video Swift concurrency: Behind the scenes interesting. It covers a lot of useful background about Swift concurrency (including its threading model). It covers a lot of other material, too, but it might help familiarize you with concepts such as await “suspension points”, “continuations”, and the like.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • What I meant by "Is there a way to turn this exclusive/synchronised access off?" was if it was possible to prevent `@MainActor` from being inherited by stored properties – rayaantaneja Mar 20 '23 at 12:49
  • Yeah, I understand the question. I am trying to politely say that it’s a profoundly bad idea. Swift concurrency gives us a simple way to ensure that we cannot have simultaneous access to some mutable property. Why would you want to turn it off?!? – Rob Mar 20 '23 at 15:48
  • But, in answer to your question, no, you can’t turn off actor-isolation of a mutable stored property in an actor-isolated type. So either move that property to a separate type that is not actor-isolated, or just give up on actors and, perhaps, `async`-`await` entirely. Hey, I get it: Sometimes all of these actor/`Sendable` errors (which I fear are going to become even more onerous in Swift 6) are annoying and a PITA to resolve (esp with Foundation not having finished it’s conversion to concurrency-friendly API), but I’m convinced it’s worth it to have code that is robustly thread-safe. – Rob Mar 21 '23 at 17:09