> 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

Reply via email to