[swift-evolution] [Concurrency] A slightly different perspective
Nathan Gray
n8gray at n8gray.org
Thu Aug 31 19:52:22 CDT 2017
That's the second time today somebody has responded to one of my messages
before I posted it. Glitch in the Matrix?
I'll post a more detailed response later on but the tl;dr is that I think
you're right on target. My first impression is that both of my major
concerns could be solved and it would enable us to arrive at the "clean"
version of the minimal example. Very nice!
On Thu, Aug 31, 2017 at 3:36 PM, Joe Groff <jgroff at apple.com> wrote:
>
> 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
>
>
--
Functional Programmer, iOS Developer, Surfs Poorly
http://twitter.com/n8gray
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170831/ea023eac/attachment.html>
More information about the swift-evolution
mailing list