I have a set of requests that are performed asynchronous. However, each next request should only start when the previous request is finished (due to data dependency).
Since all requests should just be finished in the right order, DispatchGroup()
seems of no use.
I currently implemented DispatchSemaphore()
, but I feel that this is not the best solution since I want to assure that all requests are performed in the background.
let semaphore = DispatchSemaphore(value: requests.count)
for request in requests {
apiManager().performAsyncRequest(request, failure: { error in
print(error); semaphore.signal()
}) { print(“request finished successful”)
// Next request should be performed now
semaphore.signal()
}
}
semaphore.wait()
Is there a better way to perform this?
Note: based on implementation of one of the answers below I encountered that apiManager()
is not thread safe (due to the use of a Realm database).
To keep this question clear and consider the answers in a thread safety manner with a thread safe definition of performAsyncRequest
:
public func performAsyncRequest(_ requestNumber: Int, success: @escaping (Int) -> Void)->Void {
DispatchQueue(label: "performRequest").async {
usleep(useconds_t(1000-requestNumber*200))
print("Request #\(requestNumber) starts")
success(requestNumber)
}
}
Solution with DispatchSemaphore
let semaphore = DispatchSemaphore(value: 1)
DispatchQueue(label: "requests").async {
for requestNumber in 0..<4 {
semaphore.wait()
performAsyncRequest(requestNumber) { requestNumber in
print("Request #\(requestNumber) finished")
semaphore.signal()
}
}
}
With the expected output:
Request #0 starts
Request #0 finished
Request #1 starts
Request #1 finished
Request #2 starts
Request #2 finished
Request #3 starts
Request #3 finished
Unsuccessful try with Operation
var operations = [Operation]()
for requestNumber in 0..<4 {
let operation = BlockOperation(block: {
performAsyncRequest(requestNumber) { requestNumber in
DispatchQueue.main.sync {
print("Request #\(requestNumber) finished")
}
}
})
if operations.count > 0 {
operation.addDependency(operations.last!)
}
operations.append(operation)
}
let operationQueue = OperationQueue.main
operationQueue.addOperations(operations, waitUntilFinished: false)
With incorrect output:
Request #0 starts
Request #1 starts
Request #2 starts
Request #3 starts
Request #0 finished
Request #3 finished
Request #2 finished
Request #1 finished
My feeling is that it should also be possible to get this working with Operation
, but I don't know whether it would be better than using DispatchSemaphore
.
You are on the right track with the DispatchSemaphore to ensure that an asyncronous call is not started before the previous one has finished. I would just ensure that the code that manages the calls to the asyncronous API runs in the background:
let backgroundQueue = DispatchQueue(label: "requests")
let semaphore = DispatchSemaphore(value: 1)
backgroundQueue.async {
var requestNumber = 1
for request in requests {
semaphore.wait()
let currentRequestNumber = requestNumber
print("Request launched #\(requestNumber)")
apiManager().performAsyncRequest(request,
failure: {
error in
print("Request error #\(currentRequestNumber)")
semaphore.signal()
}) {
print("Request result #\(currentRequestNumber)")
semaphore.signal()
}
requestNumber = requestNumber + 1
}
}
The code will continue execution immediatelly while the for loop runs in a background loop and starts each request after waiting for the previous one to finish.
Or if apiManager() is not thread safe:
let semaphore = DispatchSemaphore(value: 1)
var requestNumber = 1
for request in requests {
semaphore.wait()
let currentRequestNumber = requestNumber
print("Request launched #\(requestNumber)")
apiManager().performAsyncRequest(request,
failure: {
error in
print("Request error #\(currentRequestNumber)")
semaphore.signal()
}) {
print("Request result #\(currentRequestNumber)")
semaphore.signal()
}
requestNumber = requestNumber + 1
}
This has the limitation that the for loop will be in execution until the last request starts execution. But if the code you are calling is not thread safe there is no way around that.
You can use NSOperationQueue
and add each request as as operation
let firstOperation: NSOperation
let secondOperation: NSOperation
secondOperation.addDependency(firstOperation)
let operationQueue = NSOperationQueue.mainQueue()
operationQueue.addOperations([firstOperation, secondOperation], waitUntilFinished: false)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With