Here’s one of the simplest async/await programs you can write in Swift (using an @main
program entry point):
@main struct Main {
static func main() async {
print("Sleeping")
await Task.sleep(1_000_000_000) // Sleep for 1 second
print("Done")
}
}
How is this program executed? Async functions, like our main()
method, can only be called from an async context. Who creates this context on launch, and how?
The Swift runtime does, with the help of the compiler. The compiler creates an entry point for the program that looks something like this:
_runAsyncMain {
await Main.main()
}
_runAsyncMain
is a function that’s defined in the Swift concurrency runtime library. You can read the full implementation in the Swift repo. Here, I’ve simplified it to its essence (without error handling or OS differences):
public func _runAsyncMain(_ asyncFun: @escaping () async -> ()) {
Task.detached {
await asyncFun()
exit(0)
}
_asyncMainDrainQueue()
}
So the Swift runtime creates a detached task as the execution context for our main
method. It’s this hidden task that becomes the parent task of all other structured child tasks our code might create.
_runAsyncMain
then proceeds by calling _asyncMainDrainQueue
(implementation), another runtime function that passes control to GCD by calling dispatchMain
or CFRunLoopRun
(on Apple platforms).
_asyncMainDrainQueue
never returns. The program will run until our main
method returns, and then exit
. That’s it.