I think this makes the proposed async/await coroutines more viable. Building on this, if the proposed Cancelable became:
protocol ExecutionControl { /// Causes the executing coroutine to throw `TerminateCoroutine.cancelled` and also terminates all the sub-coroutines. var isCancelled: Bool { set } /// If an await exceeds timeout then executing task throws `TerminateCoroutine.timeout` and also terminates all the sub-coroutines. var timeout: DispatchTimeInterval { get set } } Then async/await coroutine would have feature parity with a typical `Future` - which would be good. PS Effectively the execution service is returning the `Future`! -- Howard. On 29 August 2017 at 09:42, Marc Schlichte via swift-evolution < swift-evolution@swift.org> wrote: > > Am 19.08.2017 um 20:33 schrieb Marc Schlichte via swift-evolution < > swift-evolution@swift.org>: > > Hi, > > to support cancellation, I propose the following changes to `beginAsync()` > and `suspendAsync()`: > > `beginAsync()` returns an object adhering to a `Cancelable` protocol: > > ``` > func beginAsync(_ body: () async throws-> Void) rethrows -> Cancelable > > protocol Cancelable { func cancel() } > ``` > > `suspendAsync()` takes a new thunk parameter: > > ``` > func suspendAsync<T>(onCancel: () -> Void, body: (cont: (T) -> Void, err: > (Error) -> Void) async -> T > ``` > > Now, when `cancel()` is invoked, the `onCancel` thunk in the current > suspension (if any) will be called. > > > > Example: > > ``` > var task: Cancelable? > > @IBAction func buttonDidClick(sender: AnyObject) { > task = beginAsync { > do { > let image = try await processImage() > imageView.image = image > } catch AsyncError.canceled { > imageView.image = nil // or some fallback image... > } catch { > // other handling > } > } > ) > > @IBAction func cancelDidClick(sender: AnyObject) { > task?.cancel() > } > > > Just adding here that instead of directly using the low-level > `beginAsync`, a Future/Promise could be used instead: > > ``` > var task: Future<UIImage>? > > @IBAction func buttonDidClick(sender: AnyObject) { > task = Future { > try await processImage() > } > do { > imageView.image = try await task!.get() > } catch AsyncError.canceled { > imageView.image = nil // or some fallback image... > } catch { > // other handling > } > } > > @iBAction func cancelDidClick(sender: AnyObject) { > task?.cancel() > } > ``` > > Of course, the init of Future would have to be changed > > convenience init(_ body: () throws async -> T) { > self.init() > task = beginAsync { > do { > self.fulfill(try await body()) > } catch { > self.fail(error) > } > } > } > > (BTW also added missing throws and try in code above) > > and `cancel()` would have to be added to `Future`: > > ``` > public func cancel() { > task?.cancel() > } > > ``` > > > func processImage() async throws -> UIImage { > // This processing should be on a background queue (or better an Actor > :-) - but ignored for this example > var cancelled = false > suspendAsync(onCancel: { > > cancelled = true > }, body: { cont, err in > while !done && !cancelled { > // do the processing on image until done or canceled > } > guard !cancelled else { err(AsyncError.canceled) } // BTW, maybe > change signature of `suspendAsync` to allow to throw here instead > cont(image) > } > } > ``` > > > ^ BTW, this should be `return await suspendAsync(…` > > > Cheers > Marc > > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution > > > > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution > >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution