Skip to content

Instantly share code, notes, and snippets.

@laprasdrum
Last active March 4, 2026 06:17
Show Gist options
  • Select an option

  • Save laprasdrum/1c8ed8745763816830498c926e32c360 to your computer and use it in GitHub Desktop.

Select an option

Save laprasdrum/1c8ed8745763816830498c926e32c360 to your computer and use it in GitHub Desktop.
Swift Concurrency: Task cancellation handling patterns
// Try on the Xcode Playground.
import Foundation
enum ItemSourceError: Error { case unavailable }
enum TestResult: Hashable {
case success
case cancelDetected(String)
case errorCaught(String)
}
struct Scenario {
private func fetchItems() async throws {
try await Task.sleep(nanoseconds: 50_000_000) // 50ms
throw ItemSourceError.unavailable
}
private func loadLikeViewModel() async -> TestResult {
do {
try await fetchItems()
return .success
} catch {
if Task.isCancelled {
return .cancelDetected(String(describing: error))
}
return .errorCaught(String(describing: error))
}
}
private func loadLikeViewModelWithCancellationError() async -> TestResult {
do {
try await fetchItems()
return .success
} catch is CancellationError {
return .errorCaught("CancellationError")
} catch {
return .errorCaught(String(describing: error))
}
}
func test() async -> TestResult {
let t = Task {
await loadLikeViewModel()
}
Task {
try await Task.sleep(nanoseconds: 50_100_000) // 50.1ms
t.cancel()
}
return await t.value
}
func testWithCancellationError() async -> TestResult {
let t = Task {
await loadLikeViewModelWithCancellationError()
}
Task {
try await Task.sleep(nanoseconds: 50_100_000) // 50.1ms
t.cancel()
}
return await t.value
}
}
struct Reporter {
var executor: @Sendable () async -> TestResult
func run() async {
var results: [TestResult] = []
for _ in 1...100 {
let result = await executor()
results.append(result)
}
var counts: [TestResult: Int] = [:]
for result in results {
counts[result, default: 0] += 1
}
print("=== Results (n = 100) ===")
for (result, count) in counts.sorted(by: { $0.value > $1.value }) {
switch result {
case .success:
print("success: \(count)")
case .cancelDetected(let error):
print("Task cancellation was detected, but error was: \(error): \(count)")
case .errorCaught(let error):
print("caught error: \(error): \(count)")
}
}
}
}
Task {
let reporter1 = Reporter(executor: {
let s = Scenario()
return await s.test()
})
await reporter1.run()
}
//Task {
// let reporter2 = Reporter(executor: {
// let s = Scenario()
// return await s.testWithCancellationError()
// })
//
// await reporter2.run()
//}
=== Results (n = 100) ===
Task cancellation was detected, but error was: unavailable: 73
caught error: unavailable: 24
Task cancellation was detected, but error was: CancellationError(): 3
=== Results (n = 100) ===
caught error: unavailable: 98
caught error: CancellationError: 2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment