Last active
March 4, 2026 06:17
-
-
Save laprasdrum/1c8ed8745763816830498c926e32c360 to your computer and use it in GitHub Desktop.
Swift Concurrency: Task cancellation handling patterns
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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() | |
| //} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| === 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| === 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