Swift Async/Await, how to bring asynchronously calculated results back to main thread

I'm relatively new to Swift, and very new to concurrency via Async/Await, so please be patient. 😀

I'm having a hard time comprehending how to do complex operations asynchronously in background threads, and then in turn bring the results back to the main thread. I'm getting various errors along the lines of "Mutation of captured var 'personName' in concurrently-executing". I've paired the issue down as simply as possible as follows, and you'll see where the compiler gives the error message.

I'd appreciate any advice on how to evolve my mental model to make this work.

Thanks! Bruce

import Foundation

actor Person {
    var myName = "Thomas Jefferson"
    var name: String {
        get {
            return myName
        }
    }
}

func main() {
    let person = Person()
    var personName: String
    print("start")

    let nameTask = Task {
        return await person.name
    }
    Task {
        do {
            personName = try await nameTask.result.get()
            // Error: Mutation of captured var 'personName' in concurrently-executing code
        } catch {
            print("error!!!")
        }
    }
    print("The person's name is \(personName)")
}

RunLoop.main.run()
main()

One other comment: I was under the impression that when a Task is called, it will suspend the calling thread until completion. If that is so, then why isn't the "personName" value fully determined after the first Task, and then there is no risk of a race condition when it is referenced in the second Task? Where is the error in my logic? thx!

one way is to call function from Task, that will set new value for personName

    let person = Person()
    var personName: String
    print("start")

    let nameTask = Task {
        return await person.name
    }
    Task {
        do {
            let getNewName = try await nameTask.result.get()
            changeName(newName: getNewName)

            // Error: Mutation of captured var 'personName' in concurrently-executing code
        } catch {
            print("error!!!")
        }
    }

func changeName(newName: String){
personName = newName
}
    print("The person's name is \(personName)")
}

I am facing the same problems. I am trying to parallelize some tasks and make sure they access a common data structure in a thread-safe manner. With every approach I try, Xcode is complaining that I try to access a reference to a captured var in a concurrently executing code. Even ChatGPT does not seem to understand Swift's concurrency model. It jumped back and forth between the same (wrong) solutions.

waldgeist, I recommend that you start a new thread for your specific issue. While the error you’re seeing is the same, the high-level task (doing work in parallel) is very different.

Use the same topic, subtopic, and tags as this thread, so that I see it.


bruceschek, It’s hard to offer specific guidance here because your paired down example is probable a little too paired down (-: It sounds like your final goal is to create an app. If so, keep in mind that the majority of your app’s UI code is isolated to the main actor, either explicitly or implicitly, and thus you’ll need to either show app code (like a view controller) or explicitly model that in your example. For the latter, you can build a ‘starter’ command-line tool like so:

import Foundation

@MainActor
class Main {

    var counter: Int = 0

    func run() async {
        counter += 1
    }
}

await Main().run()

Try reworking your example into this structure to see if it makes more sense. And if you get stuck, post an updated example and I’ll take a look.

I was under the impression that when a Task is called, it will suspend the calling thread until completion.

No. Consider this code:

func test() async {
    Task {
        // point A
    }
    // point B
}

The code at point A and the code at point B run in parallel.

Now, this can change depending on the context. Consider this:

import Foundation

@MainActor
class Main {

    var counter: Int = 0

    func run() async {
        Task {
            counter += 1
        }
        counter += 1
    }
}

await Main().run()

The two increments of counter are serialised because the closure passed to the Task initialiser is implicitly isolated to the main actor. The way that works is… frankly… complex, and has actually changed in Swift 6 [1].

Share and Enjoy

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

[1] If you’re curious, see SE-0431 or, better yet, Matt Massicotte’s explanation of that.

<https://www.massicotte.org/concurrency-swift-6-se-0431>

Swift Async/Await, how to bring asynchronously calculated results back to main thread
 
 
Q