<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><br class=""><div><blockquote type="cite" class=""><div class="">On Aug 31, 2017, at 3:04 PM, Nathan Gray via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><div dir="ltr" class="">I've been following the conversations around Chris Lattner's intriguing async/await proposal and would like to offer my own take. I feel that the proposal as written is almost perfect.&nbsp; My suggestions for improving it are not highly original -- I think they have all come up already -- but I'd like to present them from my own perspective.<div class=""><br class=""></div><div class="">1. Fixing "queue confusion" *must* be part of this proposal.&nbsp; The key bit of "magic" offered by async/await over continuation passing is that you're working in a single scope.&nbsp; A single scope should execute on a single queue unless the programmer explicitly requests otherwise.&nbsp; Queue hopping is a surprising problem in a single scope, and one that there's currently no adequate solution for.</div><div class=""><br class=""></div><div class="">Also consider error handling.&nbsp; In this code it's really hard to reason about what queue the catch block will execute on!&nbsp; And what about the defer block?</div><div class=""><br class=""></div><div class="">```</div><div class=""><font face="monospace, monospace" class="">startSpinner()</font></div><div class=""><font face="monospace, monospace" class="">defer { stopSpinner() }</font></div><div class=""><span style="font-family:monospace,monospace" class="">do {</span><br class=""></div><div class=""><font face="monospace, monospace" class="">&nbsp; try await doSomeWorkOnSomeQ()</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; try await doSomeMoreWorkOnSomeOtherQ()</font></div><div class=""><font face="monospace, monospace" class="">} catch {</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; // Where am I?</font></div><div class=""><font face="monospace, monospace" class="">}</font></div><div class="">```</div><div class=""><br class=""></div><div class="">Please, please, PLEASE don't force us to litter our business logic with gobs of explicit queue-hopping code!</div><div class=""><br class=""></div><div class="">2. The proposal should include some basic coordination mechanism.&nbsp; The argument against returning a Future every time `await` is called is convincing, so my suggestion is to do it from `beginAsync`. The Future returned should only be specified by protocol. The protocol can start with minimal features -- perhaps just cancellation and progress.&nbsp; There should be a way for programmers to specify their own, more featureful, types.&nbsp;(The proposal mentions the idea of returning a Bool, which is perhaps the least-featureful Future type imaginable. :-)</div><div class=""><br class=""></div><div class="">To make this a little more concrete.&nbsp; Let's look at how these proposals can clean up Jakob Egger's excellent example of more realistic usage of async.&nbsp; Here's the original "minimal" example that Jakob identified:<br class=""></div><div class=""><br class=""></div><div class=""><div class="">```</div><div class=""><font face="monospace, monospace" class="">class ImageProcessingTask {</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; var cancelled = false</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; func process() async -&gt; Image? { … }</font></div><div class=""><font face="monospace, monospace" class="">}</font></div><div class=""><font face="monospace, monospace" class=""><br class=""></font></div><div class=""><font face="monospace, monospace" class="">var currentTask: ImageProcessingTask?</font></div><div class=""><font face="monospace, monospace" class=""><br class=""></font></div><div class=""><font face="monospace, monospace" class="">@IBAction func buttonDidClick(sender:AnyObject) {</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; currentTask?.cancelled = true</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; let task = ImageProcessingTask()</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; currentTask = task</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; beginAsync {</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; guard let image = await task.process() else { return }</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; DispatchQueue.main.async {</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; guard task.cancelled == false else { return }</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; imageView.image = image</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; }</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; }</font></div><div class=""><font face="monospace, monospace" class="">}</font></div></div><div class="">```</div><div class=""><div class=""><br class=""></div><div class="">Here's how it could look with the changes I'm proposing.&nbsp; I will start with the bare code to demonstrate the clarity, then add some comments to explain some subtle points:</div><div class=""><br class=""></div><div class="">```</div><div class=""><div class=""><font face="monospace, monospace" class="">func processImage() async -&gt; Image? { ... }</font></div><div class=""><font face="monospace, monospace" class="">weak var currentTask: Future&lt;Void&gt;?</font></div><div class=""><font face="monospace, monospace" class=""><br class=""></font></div><div class=""><font face="monospace, monospace" class="">@IBAction func buttonDidClick(sender:AnyObject) {</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; currentTask?.cancel()</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; currentTask = beginAsync { task in</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; guard let image = await processImage() else { return }</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; if !task.isCancelled {</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; imageView.image = image</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; }</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; }</font></div><div class=""><font face="monospace, monospace" class="">}</font></div></div><div class="">```</div><div class=""><br class=""></div><div class="">IMHO this is a very clean expression of the intended behavior.&nbsp; Each line is business logic -- there is no extraneous code in service of the execution model.&nbsp; Here's the annotated version:</div><div class=""><br class=""></div><div class=""><div class="">```</div><div class=""><font face="monospace, monospace" class="">func processImage() async -&gt; Image? { ... }</font></div><div class=""><font face="monospace, monospace" class="">// If ARC can be taught to keep an executing future alive we can&nbsp;</font></div><div class=""><font face="monospace, monospace" class="">// make this weak and not worry about cleanup.&nbsp; See below.</font></div><div class=""><span style="font-family:monospace,monospace" class="">weak var currentTask: Future&lt;Void&gt;?</span><br class=""></div><div class=""><font face="monospace, monospace" class=""><br class=""></font></div><div class=""><font face="monospace, monospace" class="">@IBAction func buttonDidClick(sender:AnyObject) {</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; currentTask?.cancel()</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; // `beginAsync` creates a future, returns it, and passes it into</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; // the closure's body.</font><span style="font-family:monospace,monospace" class="">&nbsp;Can use beginAsync(MyCustomFutureType)&nbsp;</span></div><div class=""><span style="font-family:monospace,monospace" class="">&nbsp; &nbsp; // for your own future type</span></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; currentTask = beginAsync { task in</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; /* You can imagine there's implicitly code like this here</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; task.retain(); &nbsp;defer { task.release() }</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; */</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; guard let image = await processImage() else { return }</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; // Guaranteed back on the main queue here w/o boilerplate.</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; if !task.isCancelled {</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; imageView.image = image</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; &nbsp; &nbsp; }</font></div><div class=""><font face="monospace, monospace" class="">&nbsp; &nbsp; }</font></div><div class=""><font face="monospace, monospace" class="">}</font></div><div class="">```</div></div><div class=""><br class=""></div><div class=""><div class="">Note that if `beginAsync` is spelled `async` (which I do like) this idea dovetails nicely with the proposal to use `async` in place of `await` to get "future-lite" behavior.&nbsp; My personal belief is that there's no such thing as "future-lite" and the ideas are largely equivalent.</div><div class=""><br class=""></div></div><div class="">This got pretty long so thanks for reading!&nbsp; I look forward to hearing your comments.</div></div></div></div></blockquote><br class=""></div><div>You're not the only one to make these observations. What do you think about the modifications I just posed here?</div><div><br class=""></div><div><a href="https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170828/039349.html" class="">https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170828/039349.html</a></div><div><br class=""></div><div>-Joe</div><br class=""></body></html>