> On Aug 31, 2017, at 3:04 PM, Nathan Gray via swift-evolution > <[email protected]> wrote: > > 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. > > 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. > > 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? > > ``` > startSpinner() > defer { stopSpinner() } > do { > try await doSomeWorkOnSomeQ() > try await doSomeMoreWorkOnSomeOtherQ() > } catch { > // Where am I? > } > ``` > > Please, please, PLEASE don't force us to litter our business logic with gobs > of explicit queue-hopping code! > > 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. :-) > > 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: > > ``` > class ImageProcessingTask { > var cancelled = false > func process() async -> Image? { … } > } > > var currentTask: ImageProcessingTask? > > @IBAction func buttonDidClick(sender:AnyObject) { > currentTask?.cancelled = true > let task = ImageProcessingTask() > currentTask = task > beginAsync { > guard let image = await task.process() else { return } > DispatchQueue.main.async { > guard task.cancelled == false else { return } > imageView.image = image > } > } > } > ``` > > 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: > > ``` > func processImage() async -> Image? { ... } > weak var currentTask: Future<Void>? > > @IBAction func buttonDidClick(sender:AnyObject) { > currentTask?.cancel() > currentTask = beginAsync { task in > guard let image = await processImage() else { return } > if !task.isCancelled { > imageView.image = image > } > } > } > ``` > > 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: > > ``` > func processImage() async -> Image? { ... } > // If ARC can be taught to keep an executing future alive we can > // make this weak and not worry about cleanup. See below. > weak var currentTask: Future<Void>? > > @IBAction func buttonDidClick(sender:AnyObject) { > currentTask?.cancel() > // `beginAsync` creates a future, returns it, and passes it into > // the closure's body. Can use beginAsync(MyCustomFutureType) > // for your own future type > currentTask = beginAsync { task in > /* You can imagine there's implicitly code like this here > task.retain(); defer { task.release() } > */ > guard let image = await processImage() else { return } > // Guaranteed back on the main queue here w/o boilerplate. > if !task.isCancelled { > imageView.image = image > } > } > } > ``` > > 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. > > This got pretty long so thanks for reading! I look forward to hearing your > comments.
You're not the only one to make these observations. What do you think about the modifications I just posed here? https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170828/039349.html -Joe
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
