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

Jakob Egger jakob at eggerapps.at
Sat Aug 19 06:56:40 CDT 2017


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

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

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


More information about the swift-evolution mailing list