[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