What actually happens when you call a nonisolated method from an isolated one?

let’s say i have some actor methods such as:

actor A
{
    init() {}
}
extension A
{
    func a() async
    {
        await self.b()
    }

    nonisolated
    func b() async
    {
        await self.c()
    }

    func c() async
    {
    }
}

naïvely, i would think that

    func a() async
    {
        await self.b()
    }

is the same as

    func a()
    {
        self.c()
    }

but is it really?

Hm, the "the same" is really implying a lot here... what exactly are you asking about here?

Having an await vs not having an await IS different because an await is a potential suspension point which may suspend, cause interleaving etc. Given just these snippets, the primary difference is the potential for suspension (and interleaving!) is different.

Performance and runtime wise it's also different. This proposal explains it in depth: https://github.com/apple/swift-evolution/blob/main/proposals/0338-clarify-execution-non-actor-async.md -- nonisolated forces a hop to the global executor. you left the actor in b.

The question asks if it's the same as a() { c() } but that won't compile using the exact code given -- I'm assuming you mean c being not async.

Task executors which should be getting an SE review soon change how nonisolated works though if a task executor is present, so this is also a good read to dive into: Task executor preference by ktoso · Pull Request #2187 · apple/swift-evolution · GitHub

4 Likes

wow, i was not aware of that! it makes sense though, that makes it easier to understand where things are running.

2 Likes