[swift-evolution] [Concurrency] Fixing race conditions in async/await example

Thomas tclementdev at free.fr
Sat Aug 19 07:20:54 CDT 2017


Maybe this will be handled more gracefully via the actor model.

1. if you're calling from an actor, you'd be called back on its internal queue. If you're calling from the 'main actor' (or just the main thread), you'd be called back on the main queue
2. you would just add a cancel() method to the actor
3. the processImage() method would call suspendAsync() and store the continuation block in an array, once the result is available it would call all the continuation blocks with the result

That would only work if we're able to send messages to the actor while previous messages to processImage() are pending. That's something that's not yet clear to me if actors can work that way.

Thomas

> On 19 Aug 2017, at 13:56, Jakob Egger via swift-evolution <swift-evolution at swift.org> wrote:
> 
> I've read async/await proposal, and I'm thrilled by the possibilities. Here's what I consider the canonical example:
> @IBAction func buttonDidClick(sender:AnyObject) {
>   beginAsync {
>     let image = await processImage()
>     imageView.image = image
>   }
> }
> This is exactly the kind of thing I will use async/await for!
> 
> But while this example looks extremely elegant, it would suffer from a number of problems in practice:
> 
> 1. There is no guarantee that you are on the main thread after `await processImage()`
> 2. There is no way to cancel processing 
> 3. Race Condition: If you click the button a second time before `processImage()` is done, two copies will run simultaneously and you don't know which image will "win".
> 
> So I wondered: What would a more thorough example look like in practice? How would I fix all these issues?
> 
> After some consideration, I came up with the following minimal example that addresses all these issues:
> 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
>     }
>   }
> }
> If my example isn't obvious, I've documented my thinking (and some alternatives) in a gist:
> https://gist.github.com/jakob/22c9725caac5125c1273ece93cc2e1e7 <https://gist.github.com/jakob/22c9725caac5125c1273ece93cc2e1e7>
> 
> Anyway, this more realistic code sample doesn't look nearly as nice any more, and I actually think this could be implemented nicer without async/await:
> 
> class ImageProcessingTask {
>   var cancelled = false
>   func process(completionQueue: DispatchQueue, completionHandler: (Image?)->()) { … }
> }
> @IBAction func buttonDidClick(sender:AnyObject) {
>   currentTask?.cancelled = true
>   let task = ImageProcessingTask()
>   currentTask = task
>   task.process(completionQueue: DispatchQueue.main) { (image) in
>     guard let image = image else { return }
>     guard task.cancelled == false else { return }
>     imageView.image = image
>   }
> }
> 
> So I wonder: What's the point of async/await if it doesn't result in nicer code in practice? How can we make async/await more elegant when calling from non-async functions?
> 
> 
> Jakob
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170819/cf7afaa2/attachment.html>


More information about the swift-evolution mailing list