Is beginAsynconly used in non-async functions to create an async context so that one can call other async functions?
How to understand the bold part of this comment about beginAsync:
Begins an asynchronous coroutine, transferring control to body until it either suspends itself for the first time with suspendAsync or completes, at which point beginAsync returns.
Can you explain it with an example?
How to implement processImage using async/await?
Since buttonDidClick in old style looks like this:
func processImage() async -> UIImage {
await DispatchQueue.global().asyncCoroutine()
// Heavy work to prepare the image
let image = UIImage()
await DispatchQueue.main.asyncCoroutine()
return image
}
What's buttonDidClick's time sequence in async/await style?
func buttonDidClick(sender: AnyObject) {
// A. code before `beginAsync`
beginAsync {
// B. code before `await`
let image = await processImage()
// C. code after `await`
imageView.image = image
}
// D. code after `beginAsync`
}
A obviously occurs first. But what about the order of B, C, and D? ADBC or ABDC?
async/await is new to me. I still have more questions, but their validity very likely depend on the answers to the questions above. Thanks in advance.
Ideally, with library support, beginAsync would be a primitive operation you rarely encounter or think about yourself, and libraries like libdispatch would provide primitives for working with async contexts. buttonDidClick could then look something like this:
var imageProcessingQueue: DispatchQueue
func buttonDidClick(sender: AnyObject) {
// use a variation of .async that schedules a () async -> () coroutine
imageProcessingQueue.asyncCoroutine {
imageView.image = await processImage()
}
}
func processImage() async -> UIImage {
// processing happens on current queue...
}
It's been a while since we last discussed that proposal, and we haven't updated it, but one key bit of feedback we got during our last discussion on it was that coroutines ought to be strongly associated with a specific execution context, such as a dispatch queue, run loop, thread pool, or other event handling mechanism. The manual queue-hopping the proposal draft enables might be clever, but queue hopping is an ongoing correctness and performance problem with libdispatch-heavy code on Apple platforms, and it would likely be very surprising to many users if code could end up running in a different context after an await.
If that's the case, you'd want to factor it so that the assignment is dispatched on the main queue, and have processImage() perform its processing on another queue. That might look something like this:
func buttonDidClick(sender: AnyObject) {
// coroutine version of dispatch_async, schedules coroutine without blocking
DispatchQueue.main.asyncCoroutine {
imageView.image = await processImage()
}
}
func processImage() async -> UIImage {
// coroutine version of dispatch_sync, suspends current coroutine to resume when
// sync coroutine returns
return await imageProcessingQueue.syncCoroutine {
// expensive processing
return resultImage
}
}
But usually we don't consider threading when writing functions like processImage. It is callers's decision to execute it on whatever thread it likes. So the real code in current style would be like this:
func buttonDidClick(sender: AnyObject) {
imageProcessingQueue.async {
let image = processImage()
DispatchQueue.main.async {
imageView.image = image
}
}
}
func processImage() -> UIImage {
// Heavy work to prepare the image
return image
}
How would such code look like after converted to async/await?
If you want to make the queue-hopping explicit, then it would end up looking more or less the same. Async/await is ultimately not a great fit for explicit handling of CPU-intensive tasks; it shines more with IO- and event-bound processing. To me, it would be more in the spirit of async to have async interfaces to CPU-bound processes like processImage do the thread or queue management behind the scenes, since that makes the operation look more like an event-driven operation from the main thread's perspective.
But I guess these two functions cannot coexist, just like a throwing function and a non-throwing function with the same name cannot coexist: compiler won't see them as overloading but redeclaration. Does that mean I have to use something like asyncProcessImage as my wrapper function's name?