It "blocks" only because your code here is sequential (precisely what structured concurrency is about), and no other work is scheduled.
BTW, that's not allowed in Swift 6 â because in this way it actually blocks a thread. You have to use Task.sleep instead, which won't block:
And then, if you'll update it properly â so that there is other work to run, it will run it:
import Foundation
func asyncFunc () async {
try? await Task.sleep(for: .seconds(5))
print("async func output")
}
print("will call async func")
await withDiscardingTaskGroup { group in
group.addTask {
await asyncFunc()
}
group.addTask {
print("some other work here")
}
}
print("did call async func")
Your main code at the top-level is still executes sequentially, since that's the goal after all. But sequential execution is not the same as blocking.
Swift saves call stack on suspension point, freeing thread for execution, and then restores when task is ready to be resumed. If Swift would block threads on each suspension point, paired with limited pool of threads it operates on, Swift Concurrency would run out of threads to execute tasks pretty easily. That's why blocking threads (and relative APIs) aren't suitable for use.
async functions are compiled into coroutines, which are basically state machines, where every synchronous part ("the code between await statements") is a separate partial synchronous function. When an async function suspends, one of those partial functions has finished, and the caller thread is running the runtime code at this point â the thread never pauses "inside" of the body of an async function.
Any single thread is sequential. And await suspends that thread IMHO. Maybe there are cooperative tasks in Swift that run on main thread (and do not create worker threads), but that is another story.
What is "the thread"? Is it a worker thread created specifically for the async function? Or is it a caller thread using cooperative multitasking? Either way, the await in the caller thread pauses its execution.
"The thread" is either the main thread or one of the threads in the cooperative thread pool created by the concurrency runtime. The total number of these is typically equal to the number of CPU cores.
FWIW there is a certain "task-to-thread model" that can be encountered in the runtime source, but I'm not sure where it's used (embedded perhaps?). Anyway, this is not the default behaviour.
This essentially creates a worker thread and continues caller thread execution. We here talking about the await keyword which IMHO suspends current thread, and everyone tells me otherwise.
This is not a question of opinion :) You could either look into the source that deals with suspension or watch the talks that specifically discuss how Swift's concurrency runtime is implemented.
You say that await blocks, I say it does not and prohibits blocking APIs to be called as well to prevent this. Two opposite things. Blocking inside async function considered to be a programming error.
Pauses function execution. Thread itself is free to schedule any other work after suspension.
(Edit: updated to relocate the move off the main thread into getX)
The main thread is not blocked during an await in f any more than it is blocked after the getX call before the completion parameter is called. And moreover, all of the wrangling to move work on/off the main thread is handled 'automatically' by virtue of the functions declaring where they should be run.
First, I said that await blocks caller thread, and not a calledasync function, which runs in a separate worker thread.
Second, blocking inside async function cannot be a programming error. You create an async function to do a lengthy processing, e.g. fetching some resource from the Web. The fetch call inside the async function is definitely an await call which blocks the async function (in other words blocks the worker thread created specifically for the async function). The whole point of creating async functions (read: worker threads) is to not block the caller thread (possibly main thread), and move blocking operations into parallel threads.
A nonisolated async function doesn't have its own dedicated thread. Nonisolated async functions are serviced by the Swift Concurrency cooperative thread pool which has a limited number of threads to service jobs submitted to the pool. When an async function hits a suspension point (i.e., an await) it is removed from scheduling contention and the thread is freed up to run other jobs submitted to the pool until the original function is unsuspended and eligible for scheduling again.
Concurrency != multi-threading. You can easily get concurrency and async/await working in a single-threaded environment. In fact Node.js and browser environment without web workers is purely single-threaded, with async/await and coroutine transformation working similarly to what the Swift compiler does under the hood. Similarly, async/await in Python can be backed with a single-threaded event-loop, no threads are blocked by await.
As for Swift itself, certain platforms it targets like WASI can be single-threaded (see SWIFT_CONCURRENCY_GLOBAL_EXECUTOR=singlethreaded setting), and async/await works perfectly fine there, no threads are blocked, since blocking the only thread would just hang the whole program.
At some point (still?) IIRC that setting was also enabled when running apps in the iOS simulator to make it very obvious when you're introducing a thread-blocking dependency between tasks.
As I already said, any single thread is sequential. Suspending a function in a single thread means suspending the thread.
Yes. On a different thread, created or reused from a pool specifically for the called async function. A caller thread is awaiting, that is blocked or suspended or whatever - it does not execute until the async function is finished.
Means suspending the function, not thread. Thread can run other work (as @Max_Desiatov made an example of single-threaded environment). If you suspend the thread, then this thread cannot perform other tasks, which is not the case.
Exactly â reused from the pool. Caller thread can be reused from the pool after function execution has been suspended.
When you call DispatchQueue.global().async from main thread, you do not block main thread unless work is done, main thread becomes available almost immediately. await work in exactly same way as @Jumhyn has illustrated few posts ago.