|
1 | | -/// Suspends the current task, then calls the given closure with an unsafe throwing continuation for the current task. |
| 1 | +/// Suspends the current task, then calls the given closure with a throwing continuation for the current task. |
2 | 2 | /// Continuation is cancelled with error if current task is cancelled and cancellation handler is immediately invoked. |
3 | 3 | /// |
4 | | -/// This operation cooperatively checks for cancellation and reacting to it by cancelling the unsafe throwing continuation with an error |
| 4 | +/// This operation cooperatively checks for cancellation and reacting to it by cancelling the throwing continuation with an error |
5 | 5 | /// and the cancellation handler is always and immediately invoked after that. |
6 | 6 | /// For example, even if the operation is running code that never checks for cancellation, |
7 | 7 | /// a cancellation handler still runs and provides a chance to run some cleanup code. |
8 | 8 | /// |
9 | 9 | /// - Parameters: |
10 | 10 | /// - handler: A closure that is called after cancelling continuation. |
11 | 11 | /// You must not resume the continuation in closure. |
12 | | -/// - fn: A closure that takes an `UnsafeContinuation` parameter. |
| 12 | +/// - fn: A closure that takes a throwing continuation parameter. |
13 | 13 | /// You must resume the continuation exactly once. |
14 | 14 | /// |
15 | | -/// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. |
16 | 15 | /// - Returns: The value passed to the continuation. |
| 16 | +/// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. |
17 | 17 | /// |
18 | 18 | /// - Important: The continuation provided in cancellation handler is already resumed with cancellation error. |
19 | 19 | /// Trying to resume the continuation here will cause runtime error/unexpected behavior. |
20 | | -func withUnsafeThrowingContinuationCancellationHandler<T: Sendable>( |
21 | | - handler: @Sendable (UnsafeContinuation<T, Error>) -> Void, |
22 | | - _ fn: (UnsafeContinuation<T, Error>) -> Void |
23 | | -) async throws -> T { |
24 | | - typealias Continuation = UnsafeContinuation<T, Error> |
25 | | - let wrapper = ContinuationWrapper<Continuation>() |
| 20 | +func withThrowingContinuationCancellationHandler<C: ThrowingContinuable>( |
| 21 | + handler: @Sendable (C) -> Void, |
| 22 | + _ fn: (C) -> Void |
| 23 | +) async throws -> C.Success where C.Failure == Error { |
| 24 | + let wrapper = ContinuationWrapper<C>() |
26 | 25 | let value = try await withTaskCancellationHandler { |
27 | 26 | guard let continuation = wrapper.value else { return } |
28 | 27 | wrapper.cancel(withError: CancellationError()) |
29 | 28 | handler(continuation) |
30 | | - } operation: { () -> T in |
31 | | - let value = try await withUnsafeThrowingContinuation { |
32 | | - (c: Continuation) in |
33 | | - wrapper.value = c |
34 | | - fn(c) |
| 29 | + } operation: { () -> C.Success in |
| 30 | + let value = try await C.with { continuation in |
| 31 | + wrapper.value = continuation |
| 32 | + fn(continuation) |
35 | 33 | } |
36 | 34 | return value |
37 | 35 | } |
@@ -85,10 +83,80 @@ protocol Continuable: Sendable { |
85 | 83 | func resume(with result: Result<Success, Failure>) |
86 | 84 | } |
87 | 85 |
|
88 | | -extension UnsafeContinuation: Continuable {} |
89 | | - |
90 | | -extension UnsafeContinuation where E == Error { |
| 86 | +extension Continuable where Failure == Error { |
91 | 87 | /// Cancel continuation by resuming with cancellation error. |
92 | 88 | @inlinable |
93 | 89 | func cancel() { self.resume(throwing: CancellationError()) } |
94 | 90 | } |
| 91 | + |
| 92 | +extension UnsafeContinuation: Continuable {} |
| 93 | +extension CheckedContinuation: Continuable {} |
| 94 | + |
| 95 | +protocol ThrowingContinuable: Continuable { |
| 96 | + /// The type of error to resume the continuation with in case of failure. |
| 97 | + associatedtype Failure = Error |
| 98 | + /// Suspends the current task, then calls the given closure |
| 99 | + /// with a throwing continuation for the current task. |
| 100 | + /// |
| 101 | + /// The continuation can be resumed exactly once, |
| 102 | + /// subsequent resumes have different behavior depending on type implemeting. |
| 103 | + /// |
| 104 | + /// - Parameter fn: A closure that takes the throwing continuation parameter. |
| 105 | + /// You can resume the continuation exactly once. |
| 106 | + /// |
| 107 | + /// - Returns: The value passed to the continuation by the closure. |
| 108 | + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. |
| 109 | + @inlinable |
| 110 | + static func with(_ fn: (Self) -> Void) async throws -> Success |
| 111 | +} |
| 112 | + |
| 113 | +extension UnsafeContinuation: ThrowingContinuable where E == Error { |
| 114 | + /// Suspends the current task, then calls the given closure |
| 115 | + /// with an unsafe throwing continuation for the current task. |
| 116 | + /// |
| 117 | + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. |
| 118 | + /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. |
| 119 | + /// |
| 120 | + /// - Parameter fn: A closure that takes an `UnsafeContinuation` parameter. |
| 121 | + /// You must resume the continuation exactly once. |
| 122 | + /// |
| 123 | + /// - Returns: The value passed to the continuation by the closure. |
| 124 | + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. |
| 125 | + @inlinable |
| 126 | + static func with(_ fn: (UnsafeContinuation<T, E>) -> Void) async throws -> T |
| 127 | + { |
| 128 | + return try await withUnsafeThrowingContinuation(fn) |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +extension CheckedContinuation: ThrowingContinuable where E == Error { |
| 133 | + /// Suspends the current task, then calls the given closure |
| 134 | + /// with a checked throwing continuation for the current task. |
| 135 | + /// |
| 136 | + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. |
| 137 | + /// `CheckedContinuation` logs messages proving additional info on these errors. |
| 138 | + /// Once all errors resolved, use `UnsafeContinuation` in release mode to benefit improved performance |
| 139 | + /// at the loss of additional runtime checks. |
| 140 | + /// |
| 141 | + /// - Parameter fn: A closure that takes a `CheckedContinuation` parameter. |
| 142 | + /// You must resume the continuation exactly once. |
| 143 | + /// |
| 144 | + /// - Returns: The value passed to the continuation by the closure. |
| 145 | + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. |
| 146 | + @inlinable |
| 147 | + static func with(_ body: (CheckedContinuation<T, E>) -> Void) async throws |
| 148 | + -> T |
| 149 | + { |
| 150 | + return try await withCheckedThrowingContinuation(body) |
| 151 | + } |
| 152 | +} |
| 153 | + |
| 154 | +#if DEBUG || ASYNCOBJECTS_USE_CHECKEDCONTINUATION |
| 155 | +/// The continuation type used in package in `DEBUG` mode |
| 156 | +/// or if `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` flag turned on. |
| 157 | +typealias GlobalContinuation<T, E: Error> = CheckedContinuation<T, E> |
| 158 | +#else |
| 159 | +/// The continuation type used in package in `RELEASE` mode |
| 160 | +///and in absence of `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` flag. |
| 161 | +typealias GlobalContinuation<T, E: Error> = UnsafeContinuation<T, E> |
| 162 | +#endif |
0 commit comments