Post

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.

What is a Suspension Point in Swift Concurrency?

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:

  1. Call another async function (via await).
  2. Use explicit suspension helpers like Task.yield(), Task.sleep(_:), or Task.checkCancellation().
  3. Enter structured concurrency APIs such as async let, TaskGroup, or withTaskCancellationHandler.

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 CancellationError if needed or updates the cancellation status observable via Task.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 a CancellationError if the task was cancelled.

Summary

  • Suspension points are the only times Swift Concurrency checks for task cancellations.
  • Task.isCancelled reflects cancellations since the last suspension point.
  • To effectively handle cancellations, rely on existing await calls 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! 🙏

This post is licensed under CC BY 4.0 by the author.