Cooperative cancellation

Concurrency Mid-level Swift 5.7+ iOS 16+ Reviewed By Alok Singh

Predict the output

The task is cancelled after about 250 ms. What prints?

let task = Task {
    for i in 1...5 {
        print("step \(i)")
        try? await Task.sleep(for: .milliseconds(100))
    }
    print("done")
}
try await Task.sleep(for: .milliseconds(250))
task.cancel()
Options
Hint 1 of 2

What does try? do with an error?

Hint 2 of 2

Does cancel() stop code by force?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: B All five steps and done; steps 4 and 5 print almost instantly after the cancel

Correct. After the cancel, each Task.sleep throws at once, try? swallows the error, and the loop keeps going.

Why not the others?
  • A step 1, step 2, step 3, and nothing else. cancel() doesn't stop the task. It only sets a flag that code must check.
  • C step 1 to step 3, then done. Nothing breaks out of the loop. That would need a cancellation check or a try that propagates.
  • D The program crashes with CancellationError. try? turns the thrown error into nil, so nothing propagates.
Shared axis: cancelling at 0.25 s doesn't stop the loop; steps 4 and 5 print immediately because each sleep throws at once and try? swallows it.0 s0.5 s1 sWithout cancelsteps 1–50.5 sCancel at 0.25 ssteps 1–3steps 4–5 + done (instant)0.25 s
Shared axis: cancelling at 0.25 s doesn't stop the loop; steps 4 and 5 print immediately because each sleep throws at once and try? swallows it.
Text version
Timeline as text, elapsed time t in seconds
VersionWhat runs whenFinishes at
Without cancel
  • steps 1–5 from t = 0.0 s to t = 0.5 s
t = 0.5 s
Cancel at 0.25 s
  • steps 1–3 from t = 0.0 s to t = 0.25 s
  • steps 4–5 + done (instant) at t = 0.25 s
t = 0.25 s

30-second answer

Cancellation in Swift is cooperative. cancel() only sets a flag on the task and its children; nothing is stopped by force. Code must check with Task.isCancelled or try Task.checkCancellation(), and many system APIs such as Task.sleep and URLSession throw CancellationError when they see the flag.

Practise saying it · 45 s. No recording, just a timer

Explain how cooperative cancellation works in Swift.

Say your answer out loud, then check which of these points you covered:

  • cancel() sets a flag; nothing is forced
  • Code checks isCancelled / checkCancellation
  • Children are cancelled with the parent
  • try? swallows CancellationError

Deep dive

Cancelling a parent marks every child task as cancelled too.

Swallowing errors with try? also swallows CancellationError, so the work carries on.

Use withTaskCancellationHandler to cancel underlying work immediately, for example a socket.

SwiftUI's .task cancels its task automatically when the view disappears.

Checking cancellation Type-checked · Swift 6.4
func process(_ items: [Item]) async throws {
    for (index, item) in items.enumerated() {
        if index.isMultiple(of: 50) { try Task.checkCancellation() }
        try await handle(item)
    }
}

Common mistakes

  • Expecting cancel() to stop a task immediately.
  • Using try? around Task.sleep in a loop and never checking cancellation.
  • Treating CancellationError as a failure and showing an error alert.

Senior take & follow-ups

Seniors point out that try? is the usual reason cancellation 'doesn't work', and that cancellation should never surface as a user-facing error.

Follow-up questions

Think of your answer first, then open the hint.

  1. How do you make a CPU-heavy loop cancellable?

    Show hint

    Call try Task.checkCancellation() every few iterations.

  2. Should a cancelled search show an error?

    Show hint

    No. Treat CancellationError as a normal outcome.

Suggest a fix if something here is wrong or out of date.