What is a Suspension Point in Swift Concurrency?
Learn about suspension points in modern Swift Concurrency, how they work, and why they're crucial for effective task management.
In Swift’s async/await world, suspension points are crucial for managing concurrency efficiently. These points allow your async functions to temporarily “pause” and let the system perform other tasks or check if the current task has been cancelled. Understanding suspension points ensures your Swift apps handle concurrency and cancellations effectively.
What exactly is a “suspension point”?
A suspension point is any location within an async function where Swift might pause execution temporarily. These include scenarios where you:
- Call another async function (via
await). - Use explicit suspension helpers like
Task.yield(),Task.sleep(_:), orTask.checkCancellation(). - Enter structured concurrency APIs such as
async let,TaskGroup, orwithTaskCancellationHandler.
At suspension points, Swift:
- Saves the current execution context.
- Returns control of the thread to the scheduler.
- Checks if the task has been cancelled.
- Throws a
CancellationErrorif needed or updates the cancellation status observable viaTask.isCancelled. - Restores the context and continues execution when resumed.
What does “since the last suspension point” mean?
Swift concurrency uses cooperative cancellation, meaning the runtime only checks the cancellation status at suspension points. Consequently:
- Heavy, CPU-bound synchronous loops won’t immediately respond to cancellation requests.
- Once your function hits the next suspension point (
await,yield(), etc.), Swift recognizes cancellation requests and can react appropriately.
Thus, Task.isCancelled will return true if the cancellation request occurred after the last suspension point. Observing this helps you halt further unnecessary work.
Examples
1. Simple Network Fetch Loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func fetchAll(_ urls: [URL]) async {
for url in urls {
// Suspension point: network fetch
let data = await URLSession.shared.data(from: url).0
// Immediately after suspension, check cancellation
if Task.isCancelled {
print("Fetch loop cancelled; stopping early.")
break
}
process(data)
}
}
- Suspension:
await data(from:) - Checkpoint: After await, the task checks if it was cancelled while fetching data.
2. Explicit Yield in CPU-bound Work
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func countPrimes(upTo n: Int) async -> [Int] {
var primes: [Int] = []
for i in 2...n {
if await isPrime(i) {
primes.append(i)
}
// Force suspension and cancellation check every 1,000 iterations
if i % 1_000 == 0 {
await Task.yield()
if Task.isCancelled {
print("Prime counting cancelled at i = \(i)")
break
}
}
}
return primes
}
- Inserting
await Task.yield()creates explicit suspension points, allowing cancellation checks during heavy computations.
3. Throwing on Cancellation
1
2
3
4
5
6
func doWork() async throws {
for step in 1...10 {
try Task.checkCancellation() // Suspension and cancellation check
// Perform step work...
}
}
- Here,
Task.checkCancellation()explicitly throws aCancellationErrorif the task was cancelled.
Summary
- Suspension points are the only times Swift Concurrency checks for task cancellations.
Task.isCancelledreflects cancellations since the last suspension point.- To effectively handle cancellations, rely on existing
awaitcalls or insert explicit suspension points (Task.yield(),.sleep(), or.checkCancellation()).
This cooperative cancellation model enables Swift’s scheduler to manage thousands of concurrent tasks efficiently, minimizing unnecessary overhead and maximizing performance.
☕ Support My Work
If you found this post helpful and want to support more content like this, you can buy me a coffee!
Your support helps me continue creating useful articles and tips for fellow developers. Thank you! 🙏