[swift 6.2] async function without async context

If i have an async function. But its context has no async function calls. Will the async function run in the background?

@main
struct Main {
    static func main() async {
        await printRunning()
    }
}

@concurrent
func printRunning() async {
    print("running...")
}

Will printRunning() run in the background if its context has no async calls, but as long as it is marked async?

if the @concurrent function is called from an actor-isolated context, then by default, it will first switch to the concurrent executor before beginning execution. if the caller is not running on an actor, i would expect no additional 'executor hop' to occur. the first point is somewhat explicitly spelled out in SE-0461:

The @concurrent attribute is an explicit spelling for the behavior of async functions in language modes <= Swift 6. @concurrent indicates that calling the function always switches off of an actor to run, so the function will run concurrently with other tasks on the caller's actor

the second point, what about the caller is nonisolated and the @concurrent function has no async context inside, for example, just a print call?

I think you probably misunderstand what an asynchronous function's execution context means. Take printRunning() as an example, since it's declared as an async function, it has an execution context (e.g. where it's executed) by definition. So your assumption that it might have no context is invalid.

That said, I understand what you really wanted to ask. Where an asynchronous function is executed isn't affected by its function body containing asynchronous calls or not. It's entirely determined by other factors (e.g. its isolation, or @concurrent, etc). In your example, since printRunning() is @concurrent, I think it always run concurrently (that is, in background).

Note: @jamieQ mentioned a situation where he thinks a @concurrent might run synchronously. I don't think that behavior is mentioned in SE proposals. I doubt it unless there are experiments proving it

I asked such a question. If you read @jamieQ 's answer. Because if the async function inside has no async calls. It used to be no switching occur as I read from somewhere. I may be wrong. So, I want to clarify the behaviour.

There is no switching inside the function, but when the function itself is executed, there is executor switching. I think the latter is what you asked about?

i know inside has no switching calls. I am asking outside, the function itself.

Since printRunning() is @concurrent and async, it will run on the background regardless of whether the body of printRunning() contains any await calls or not.

If printRunning() is called from an actor (like @MainActor, in your example), the caller will suspend, then a thread from the global concurrency thread pool ("the background") will pick up the work and execute the body of printRunning().

If printRunning() isn't called from an actor, and is instead called from a thread from the global concurrency pool, the caller may not suspend before executing the body of printRunning(), as it's already in the right isolation domain ("the background").

That was the behaviour prior to SE-0338, which offloaded all nonisolated async functions to the global concurrent executor. The problem that lead to this proposal was that such functions has no deterministic context to run within at all, the fact that they did stay with the current executor was just coincidence.

SE-0461 has refined this by allowing two behaviours, with @concurrent always offloading work from the current executor, and other (with feature flag on) functions will stay unless some call inside it won't (and if) switch to another isolation.

You can actually see the behaviour by yourself pretty easy: write second printRunning without @concurrent and add isolation assertion to both of them. Version with @concurrent will fail.

1 Like