<div dir="ltr">I think this makes the proposed async/await coroutines more viable. Building on this, if the proposed Cancelable became:<div><br></div><div> protocol ExecutionControl {</div><div> /// Causes the executing coroutine to throw `TerminateCoroutine.cancelled` and also terminates all the sub-coroutines.</div><div> var isCancelled: Bool { set }</div><div><br></div><div> /// If an await exceeds timeout then executing task throws `TerminateCoroutine.timeout` and also terminates all the sub-coroutines.</div><div> var timeout: DispatchTimeInterval { get set }</div><div> }<br></div><div><br></div><div>Then async/await coroutine would have feature parity with a typical `Future` - which would be good.</div><div><br></div><div>PS Effectively the execution service is returning the `Future`!</div></div><div class="gmail_extra"><br clear="all"><div><div class="gmail_signature" data-smartmail="gmail_signature"> -- Howard.<br></div></div>
<br><div class="gmail_quote">On 29 August 2017 at 09:42, Marc Schlichte via swift-evolution <span dir="ltr"><<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><br><div><blockquote type="cite"><div>Am 19.08.2017 um 20:33 schrieb Marc Schlichte via swift-evolution <<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>>:</div><br class="m_-3432277472789290258Apple-interchange-newline"><div><div>Hi,<br><br>to support cancellation, I propose the following changes to `beginAsync()` and `suspendAsync()`:<br><br>`beginAsync()` returns an object adhering to a `Cancelable` protocol:<br><br>```<br>func beginAsync(_ body: () async throws-> Void) rethrows -> Cancelable<br><br>protocol Cancelable { func cancel() }<br>```<br><br>`suspendAsync()` takes a new thunk parameter:<br><br>```<br>func suspendAsync<T>(onCancel: () -> Void, body: (cont: (T) -> Void, err: (Error) -> Void) async -> T <br>```<br><br>Now, when `cancel()` is invoked, the `onCancel` thunk in the current suspension (if any) will be called.</div></div></blockquote><blockquote type="cite"><div><div><br><br>Example:<br><br>```<br>var task: Cancelable?<br><br>@IBAction func buttonDidClick(sender: AnyObject) {<br> task = beginAsync {<br> do {<br> let image = try await processImage()<br> imageView.image = image <br> } catch AsyncError.canceled {<br> imageView.image = nil // or some fallback image...<br> } catch {<br> // other handling<br> }<br> } <br>)<br><br>@IBAction func cancelDidClick(sender: AnyObject) {<br> task?.cancel()<br>}<br></div></div></blockquote><div><br></div><div>Just adding here that instead of directly using the low-level `beginAsync`, a Future/Promise could be used instead:</div><div><br></div><div>```</div><div>var task: Future<UIImage>?</div><div><br></div><div>@IBAction func buttonDidClick(sender: AnyObject) {</div><div> task = Future {</div><div> try await processImage()</div><div> }</div><div> do {</div><div> imageView.image = try await task!.get()</div><div> } catch AsyncError.canceled {</div><div> imageView.image = nil // or some fallback image...</div><div> } catch {</div><div> // other handling</div><div> }</div><div>}</div><div><br></div><div>@iBAction func cancelDidClick(sender: AnyObject) {</div><div> task?.cancel()</div><div>}</div><div>```</div><div><br></div><div>Of course, the init of Future would have to be changed</div><div><br></div><div><pre style="box-sizing:border-box;font-family:SFMono-Regular,Consolas,'Liberation Mono',Menlo,Courier,monospace;font-size:13.600000381469727px;margin-top:0px;margin-bottom:0px;line-height:1.45;word-wrap:normal;padding:16px;overflow:auto;background-color:rgb(246,248,250);border-top-left-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px;border-bottom-left-radius:3px;word-break:normal;color:rgb(36,41,46)"> <span class="m_-3432277472789290258pl-k" style="box-sizing:border-box;color:rgb(215,58,73)">convenience</span> <span class="m_-3432277472789290258pl-k" style="box-sizing:border-box;color:rgb(215,58,73)">init</span>(<span class="m_-3432277472789290258pl-en" style="box-sizing:border-box;color:rgb(111,66,193)">_</span> <span class="m_-3432277472789290258pl-smi" style="box-sizing:border-box">body</span>: () throws async <span class="m_-3432277472789290258pl-k" style="box-sizing:border-box;color:rgb(215,58,73)">-></span> T) {
<span class="m_-3432277472789290258pl-c1" style="box-sizing:border-box;color:rgb(0,92,197)">self</span>.<span class="m_-3432277472789290258pl-k" style="box-sizing:border-box;color:rgb(215,58,73)">init</span>()
task = beginAsync {
<span class="m_-3432277472789290258pl-k" style="box-sizing:border-box;color:rgb(215,58,73)">do</span> {
<span class="m_-3432277472789290258pl-c1" style="box-sizing:border-box;color:rgb(0,92,197)">self</span>.<span class="m_-3432277472789290258pl-c1" style="box-sizing:border-box;color:rgb(0,92,197)">fulfill</span>(try await <span class="m_-3432277472789290258pl-c1" style="box-sizing:border-box;color:rgb(0,92,197)">body</span>())
} <span class="m_-3432277472789290258pl-k" style="box-sizing:border-box;color:rgb(215,58,73)">catch</span> {
<span class="m_-3432277472789290258pl-c1" style="box-sizing:border-box;color:rgb(0,92,197)">self</span>.<span class="m_-3432277472789290258pl-c1" style="box-sizing:border-box;color:rgb(0,92,197)">fail</span>(error)
}
}
}</pre><div>(BTW also added missing throws and try in code above)</div><div><br></div><div>and `cancel()` would have to be added to `Future`:</div><div><br></div><div>```</div><div>public func cancel() {</div><div> task?.cancel()</div><div>}</div><div><br></div><div>```</div></div><div><br></div><blockquote type="cite"><div><div><br>func processImage() async throws -> UIImage {<br> // This processing should be on a background queue (or better an Actor :-) - but ignored for this example<br> var cancelled = false<br> suspendAsync(onCancel: {<br></div></div></blockquote></div><div><blockquote type="cite"><div><div> cancelled = true<br> }, body: { cont, err in<br> while !done && !cancelled {<br> // do the processing on image until done or canceled<br> }<br> guard !cancelled else { err(AsyncError.canceled) } // BTW, maybe change signature of `suspendAsync` to allow to throw here instead<br> cont(image)<br> }<br>}<br>```<br></div></div></blockquote><div><br></div>^ BTW, this should be `return await suspendAsync(…`<br><blockquote type="cite"><div><div><br>Cheers<br>Marc<br><br>______________________________<wbr>_________________<br>swift-evolution mailing list<br><a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a><br><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" target="_blank">https://lists.swift.org/<wbr>mailman/listinfo/swift-<wbr>evolution</a><br></div></div></blockquote></div><br></div><br>______________________________<wbr>_________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank">https://lists.swift.org/<wbr>mailman/listinfo/swift-<wbr>evolution</a><br>
<br></blockquote></div><br></div>