The output of the below code is different from what is anticipated

I was expecting all of the code to run in main thread, but both task and async function is working in backfround thread. Any explanations?

Replies

You could try tagging the functions as

@MainActor func func1() {}

  • I understand @MainActor will force it to run in main thread, but shouldn't it run in main thread irrespective of that? Since the Task was spawned in main thread?

Add a Comment

shouldn't it run in main thread irrespective of that? Since the Task was spawned in main thread?

IMO the most critical insight into Swift concurrency is that it detects concurrency problems at compile time. So, when you make a statement like that, it’s important to ask yourself “How does the compiler know this?” And in this case the compiler doesn’t know this, which is why you’re running into problems O-:

The compiler does its concurrency checking based on the static information available to it at compile time. So, for something to be isolated to the main actor there has to be something that tells the compiler to run it on the main thread. In this case there is not.

When you run async code via Task { … }, you’ll see two different behaviours:

  • If the surrounding context is an actor, the code is isolated to that actor.

  • If not, the code is not isolated.

This is actually quite tricky for two reasons:

  • The behaviour of Task { … } is subtle. It’s based on the @_inheritActorContext attribute. That’s not officially part of the language, but there are ongoing efforts on Swift Evolution to fix that. Specifically, SE-0420 Inheritance of actor isolation.

  • You’re using top-level code. IME it’s best to avoid top-level code where possible.

Let’s start out by ignoring top-level code. Consider this program:

@MainActor
class MainActorClass {
    func run() {
    }
    func inheritedIsolation() {
        Task {
            self.run()
        }
    }
}

func test() {
    Task {
        let o = MainActorClass()
        o.run()
     // ^ Expression is 'async' but is not marked with 'await'
    }
}

I put it into a new macOS > Command Line Tool target using Xcode 15.3 with strict concurrency checking enabled. The code in inheritedIsolation() compiles because Task inherits the isolation from the surrounding MainActorClass. OTOH, the code in test() does not, so the code runs non-isolated. That triggers an error when you try to run the actor-isolated run() method without an await.


Bringing top-level code back into the mix produces an interesting result. I extend the code above like so:

… code above …

Task {
    let o = MainActorClass()
    o.run()
}

This works because Task inherits main-actor isolation from the surrounding top-level context. I think this is fallout from SE-0343 Concurrency in Top-level Code but it’s not 100% clear because, as I mentioned above, the semantics of @_inheritActorContext are a bit fuzzy.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"