<div dir="ltr">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. 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><br></div><div>1. Fixing "queue confusion" *must* be part of this proposal. The key bit of "magic" offered by async/await over continuation passing is that you're working in a single scope. A single scope should execute on a single queue unless the programmer explicitly requests otherwise. Queue hopping is a surprising problem in a single scope, and one that there's currently no adequate solution for.</div><div><br></div><div>Also consider error handling. In this code it's really hard to reason about what queue the catch block will execute on! And what about the defer block?</div><div><br></div><div>```</div><div><font face="monospace, monospace">startSpinner()</font></div><div><font face="monospace, monospace">defer { stopSpinner() }</font></div><div><span style="font-family:monospace,monospace">do {</span><br></div><div><font face="monospace, monospace"> try await doSomeWorkOnSomeQ()</font></div><div><font face="monospace, monospace"> try await doSomeMoreWorkOnSomeOtherQ()</font></div><div><font face="monospace, monospace">} catch {</font></div><div><font face="monospace, monospace"> // Where am I?</font></div><div><font face="monospace, monospace">}</font></div><div>```</div><div><br></div><div>Please, please, PLEASE don't force us to litter our business logic with gobs of explicit queue-hopping code!</div><div><br></div><div>2. The proposal should include some basic coordination mechanism. 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. There should be a way for programmers to specify their own, more featureful, types. (The proposal mentions the idea of returning a Bool, which is perhaps the least-featureful Future type imaginable. :-)</div><div><br></div><div>To make this a little more concrete. Let's look at how these proposals can clean up Jakob Egger's excellent example of more realistic usage of async. Here's the original "minimal" example that Jakob identified:<br></div><div><br></div><div><div>```</div><div><font face="monospace, monospace">class ImageProcessingTask {</font></div><div><font face="monospace, monospace"> var cancelled = false</font></div><div><font face="monospace, monospace"> func process() async -> Image? { … }</font></div><div><font face="monospace, monospace">}</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">var currentTask: ImageProcessingTask?</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">@IBAction func buttonDidClick(sender:AnyObject) {</font></div><div><font face="monospace, monospace"> currentTask?.cancelled = true</font></div><div><font face="monospace, monospace"> let task = ImageProcessingTask()</font></div><div><font face="monospace, monospace"> currentTask = task</font></div><div><font face="monospace, monospace"> beginAsync {</font></div><div><font face="monospace, monospace"> guard let image = await task.process() else { return }</font></div><div><font face="monospace, monospace"> DispatchQueue.main.async {</font></div><div><font face="monospace, monospace"> guard task.cancelled == false else { return }</font></div><div><font face="monospace, monospace"> imageView.image = image</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace">}</font></div></div><div>```</div><div><div><br></div><div>Here's how it could look with the changes I'm proposing. I will start with the bare code to demonstrate the clarity, then add some comments to explain some subtle points:</div><div><br></div><div>```</div><div><div><font face="monospace, monospace">func processImage() async -> Image? { ... }</font></div><div><font face="monospace, monospace">weak var currentTask: Future<Void>?</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">@IBAction func buttonDidClick(sender:AnyObject) {</font></div><div><font face="monospace, monospace"> currentTask?.cancel()</font></div><div><font face="monospace, monospace"> currentTask = beginAsync { task in</font></div><div><font face="monospace, monospace"> guard let image = await processImage() else { return }</font></div><div><font face="monospace, monospace"> if !task.isCancelled {</font></div><div><font face="monospace, monospace"> imageView.image = image</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace">}</font></div></div><div>```</div><div><br></div><div>IMHO this is a very clean expression of the intended behavior. Each line is business logic -- there is no extraneous code in service of the execution model. Here's the annotated version:</div><div><br></div><div><div>```</div><div><font face="monospace, monospace">func processImage() async -> Image? { ... }</font></div><div><font face="monospace, monospace">// If ARC can be taught to keep an executing future alive we can </font></div><div><font face="monospace, monospace">// make this weak and not worry about cleanup. See below.</font></div><div><span style="font-family:monospace,monospace">weak var currentTask: Future<Void>?</span><br></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">@IBAction func buttonDidClick(sender:AnyObject) {</font></div><div><font face="monospace, monospace"> currentTask?.cancel()</font></div><div><font face="monospace, monospace"> // `beginAsync` creates a future, returns it, and passes it into</font></div><div><font face="monospace, monospace"> // the closure's body.</font><span style="font-family:monospace,monospace"> Can use beginAsync(MyCustomFutureType) </span></div><div><span style="font-family:monospace,monospace"> // for your own future type</span></div><div><font face="monospace, monospace"> currentTask = beginAsync { task in</font></div><div><font face="monospace, monospace"> /* You can imagine there's implicitly code like this here</font></div><div><font face="monospace, monospace"> task.retain(); defer { task.release() }</font></div><div><font face="monospace, monospace"> */</font></div><div><font face="monospace, monospace"> guard let image = await processImage() else { return }</font></div><div><font face="monospace, monospace"> // Guaranteed back on the main queue here w/o boilerplate.</font></div><div><font face="monospace, monospace"> if !task.isCancelled {</font></div><div><font face="monospace, monospace"> imageView.image = image</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace">}</font></div><div>```</div></div><div><br></div><div><div>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. My personal belief is that there's no such thing as "future-lite" and the ideas are largely equivalent.</div><div><br></div></div><div>This got pretty long so thanks for reading! I look forward to hearing your comments.</div><div><br></div><div>Thanks,</div><div>-n8</div><div><br></div>-- <br><div class="gmail_signature">Functional Programmer, iOS Developer, Surfs Poorly<br><a href="http://twitter.com/n8gray" target="_blank">http://twitter.com/n8gray</a></div>
</div></div>