Using Swift's new async/await functionality, I want to emulate the scheduling behavior of a serial queue (similar to how one might use a DispatchQueue
or OperationQueue
in the past).
Simplifying my use case a bit, I have a series of async tasks I want to fire off from a call-site and get a callback when they complete but by design I want to execute only one task at a time (each task depends on the previous task completing).
Today this is implemented via placing Operation
s onto an OperationQueue
with a maxConcurrentOperationCount = 1
, as well as using the dependency functionality of Operation
when appropriate. I've build an async/await wrapper around the existing closure-based entry points using await withCheckedContinuation
but I'm trying to figure out how to migrate this entire approach to the new system.
Is that possible? Does it even make sense or am I fundamentally going against the intent of the new async/await concurrency system?
I've dug some into using Actor
s but as far as I can tell there's no way to truly force/expect serial execution with that approach.
--
More context - This is contained within a networking library where each Operation today is for a new request. The Operation does some request pre-processing (think authentication / token refreshing if applicable), then fires off the request and moves on to the next Operation, thus avoiding duplicate authentication pre-processing when it is not required. Each Operation doesn't technically know that it depends on prior operations but the OperationQueue's scheduling enforces the serial execution.
Adding sample code below:
// Old entry point
func execute(request: CustomRequestType, completion: ((Result<CustomResponseType, Error>) -> Void)? = nil) {
let operation = BlockOperation() {
// do preprocessing and ultimately generate a URLRequest
// We have a URLSession instance reference in this context called session
let dataTask = session.dataTask(with: urlRequest) { data, urlResponse, error in
completion?(/* Call to a function which processes the response and creates the Result type */)
dataTask.resume()
}
// queue is an OperationQueue with maxConcurrentOperationCount = 1 defined elsewhere
queue.addOperation(operation)
}
// New entry point which currently just wraps the old entry point
func execute(request: CustomRequestType) async -> Result<CustomResponseType, Error> {
await withCheckedContinuation { continuation in
execute(request: request) { (result: Result<CustomResponseType, Error>) in
continuation.resume(returning: result)
}
}
}