Async Await Series part 1: Understanding What Happens When You Write await
The await
keyword in Swift’s concurrency model is central to managing asynchronous tasks without blocking threads. To fully understand its behavior, let’s delve into its mechanics and how Swift handles it behind the scenes.
The Core Question: Does await
Block the Main Thread?
Short answer: No, await
does not block the main thread or any thread. Instead, it allows the system to pause your function at that point (a suspension point) and free up the thread for other work until the awaited task completes.
Here’s a deeper explanation:
How async
and await
Work
- Marking a Function as
async
:
- Declaring a function with
async
tells Swift that the function might suspend at certain points. - These suspension points are marked by
await
.
- Using
await
:
- When a function calls another
async
function usingawait
, Swift: - Pauses the current function and saves its call stack.
- Frees the thread to perform other tasks while waiting for the awaited function to complete.
- Resumes the function from where it paused once the awaited function finishes.
Synchronous vs Asynchronous Call Stacks
Synchronous Call Stack
- A synchronous function builds up a stack of calls:
- The function runs until completion.
- Each called function completes before the next one can proceed.
- The thread is blocked while the call stack is unwound.
For example:
func synchronousFunction() {
print("one")
slowOperation()
print("two")
}
- The thread cannot do anything else until
slowOperation()
finishes.
Asynchronous Call Stack
- With an
async
function, Swift inserts suspension points atawait
. At these points:
- The current function’s state (call stack) is saved.
- The thread becomes available for other tasks.
- Once the awaited function completes, Swift restores the saved state and resumes execution.
For example:
func asynchronousFunction() async {
print("one")
await slowAsyncOperation()
print("two")
}
await slowAsyncOperation()
pauses the function, but the thread is freed to handle other tasks.
Visualizing the Process
Synchronous Call Stack
- Imagine the call stack as a tower:
- Each function adds a block to the tower.
- The thread unwinds the stack one block at a time.
- Until the slowest operation finishes, the thread is stuck managing the stack.
Asynchronous Call Stack
- With
async
andawait
:
- At
await
, the tower’s state is saved and put aside. - The thread is freed to build a new tower for other tasks.
- When the awaited function finishes, the original tower is restored, and the function resumes.
This is why await
does not block a thread. Instead, it frees up resources until the awaited task completes.
What Happens at an await
?
Here’s the step-by-step process when await
is encountered:
Suspension Point:
- The current function pauses its execution at
await
. - The state of the call stack is saved (e.g., variable values, program counter).
Freeing the Thread:
- The thread running the paused function is freed to perform other work.
- This allows concurrent tasks to make efficient use of resources.
Resuming Execution:
- When the awaited task completes, the system restores the call stack.
- The paused function resumes from the
await
point. - Resumption may occur on the same thread or a different one, depending on the system’s decision.
Example: Non-Blocking await
Task {
print("Start")
let result = try await slowAsyncOperation()
print("Result: \(result)")
}
Output:
Start
(Result printed later)
Here:
print("Start")
executes immediately.await slowAsyncOperation()
pauses the task.- The thread is freed to handle other work.
- When
slowAsyncOperation()
finishes, the function resumes and prints the result.
Thread Utilization with await
- The system ensures that:
- The main thread is never blocked (e.g., UI remains responsive).
- Threads are used efficiently, performing other work while waiting for asynchronous tasks to complete.
Key Concepts
Suspension Points:
- Every
await
marks a point where the function may pause. - The thread running the function is freed temporarily.
Call Stack Saving:
- At
await
, the current state of the function is saved. - Swift manages this internally, allowing seamless pausing and resumption.
Structured Concurrency:
- All tasks and awaited work must complete before their parent task finishes.
- This ensures predictable, manageable execution flows.
Common Misconceptions
“Does await
block a thread?"
- No,
await
frees the thread. The paused function doesn’t occupy the thread during suspension.
“Does resumption always happen on the same thread?”
- Not necessarily. Swift may resume the function on a different thread if it’s safe and allowed.
“Does await
slow down execution?"
- Not inherently. It allows better resource utilization by freeing the thread to handle other tasks.
Summary
await
introduces suspension points where the current function pauses, freeing the thread.- Asynchronous tasks are executed efficiently, with threads shared across multiple tasks.
- Swift ensures safety and efficiency through structured concurrency and thread management.
- Understanding
await
helps you write non-blocking, responsive, and efficient code.