[swift-evolution] [Concurrency] A slightly different perspective
Joe Groff
jgroff at apple.com
Thu Aug 31 17:36:28 CDT 2017
> On Aug 31, 2017, at 3:04 PM, Nathan Gray via swift-evolution <swift-evolution at swift.org> 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170831/88e87b2d/attachment.html>
More information about the swift-evolution
mailing list