GCD - asyncAfter: How to run it synchronously - swift

I have just started reading swift and i'm currently confused of how to use threading correctly.
What i'm trying to achieve in the following block of code, is to execute the print statements inside the dispatchers, but i want to do it in order. The problem that i have, is that of course i want to do this in a background thread than the main since this is a long task, and at the same time to execute it in order while i'm giving delay in the execution. The current block executes each of the cases all together.
I have also took a look in Timer and Semaphores but without any results.
Any help or explanation of what i'm doing wrong or what should i approach will be appreciated.
let formattedSeries = ["a", "a", "b"]
let dispatchQueue = DispatchQueue(label: "taskQueue")
let a = 1000
let b = 5000
for (index, letter) in (formattedSeries.enumerated()){
switch letter {
case "a":
dispatchQueue.asyncAfter(deadline: .now() + .milliseconds(a), execute: {
print("a executed")
})
break
case "b":
dispatchQueue.asyncAfter(deadline: .now() + .milliseconds(b), execute: {
print("b executed")
})
break
default:
print("default")
}
}

You can use a dispatch group to force the evaluation of the next letter to wait for evaluation of the previous letter:
let dispatchGroup = DispatchGroup()
let dispatchQueue = DispatchQueue(label: "taskQueue")
let a = 1000
let b = 5000
let formattedSeries = "abbaabba"
print("start", Date().timeIntervalSince1970)
for (index, letter) in (formattedSeries.enumerated()){
dispatchGroup.enter()
switch letter {
case "a":
dispatchQueue.asyncAfter(deadline: .now() + .milliseconds(a), execute: {
print("a executed", Date().timeIntervalSince1970)
dispatchGroup.leave()
})
break
case "b":
dispatchQueue.asyncAfter(deadline: .now() + .milliseconds(b), execute: {
print("b executed", Date().timeIntervalSince1970)
dispatchGroup.leave()
})
break
default:
print("default")
}
dispatchGroup.wait()
}
I've added some extra output to prove that the intervals are correct. The output is
start 1580060250.3307471
a executed 1580060251.389974
b executed 1580060256.889923
b executed 1580060262.2758632
a executed 1580060263.372933
a executed 1580060264.373787
b executed 1580060269.37443
b executed 1580060274.375314
a executed 1580060275.4726748
which proves that we evaluated the letters in order, and that the async_after intervals elapse between the prints.

To execute the tasks in order you need an asynchronous operation.
Use the class AsynchronousOperation provided in this answer
Create a serial OperationQueue and set maxConcurrentOperationCount to 1
Subclass AsynchronousOperation and put the dispatchQueue.asyncAfter tasks into the main() method of the subclass. Call finish() in the closure (before or after print("a executed"))
Add the operations to the serial operation queue

Related

How to get concurrency when using AsyncLines

I'm trying to use AsyncLineSequence with Process to execute many instances of a shell script at the same time. The issue I'm seeing is that with my usage of AsyncLineSequence I'm not seeing the output of the Process invocations interweaved like I would expect. It feels like there is something fundamental I am misunderstanding as this seems like it should work to me.
Here's a reproduction in a playground
import Cocoa
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
exit(EXIT_SUCCESS)
}
func run(label: String) throws {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/yes")
let pipe = Pipe()
process.standardOutput = pipe
Task {
for try await _ in pipe.fileHandleForReading.bytes.lines {
print(label)
}
}
try process.run()
}
Task {
try run(label: "a")
}
Task {
try run(label: "b")
}
The above will print only a or b but never both. If I change to not use AsyncLineSequence like this
import Cocoa
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
exit(EXIT_SUCCESS)
}
func run(label: String) throws {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/yes")
let pipe = Pipe()
process.standardOutput = pipe
pipe.fileHandleForReading.readabilityHandler = { _ in
print(label)
}
try process.run()
}
Task {
try run(label: "a")
}
Task {
try run(label: "b")
}
The as and bs are both printed interleaved.
To add to my confusion if I use URLSession to get async lines by reading an arbitrary file it does interleave the print statements of a and b as I'd expect
import Cocoa
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
exit(EXIT_SUCCESS)
}
Task {
for try await _ in try await URLSession.shared.bytes(from: URL(fileURLWithPath: "/usr/bin/yes")).0.lines {
print("a")
}
}
Task {
for try await _ in try await URLSession.shared.bytes(from: URL(fileURLWithPath: "/usr/bin/yes")).0.lines {
print("b")
}
}
If I replace URLSession for FileHandle in the above then I am back to no interleaving and all of one file is read followed by the next
import Cocoa
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
exit(EXIT_SUCCESS)
}
Task {
for try await _ in try FileHandle(forReadingFrom: URL(fileURLWithPath: "/usr/bin/yes")).bytes.lines {
print("a")
}
}
Task {
for try await _ in try FileHandle(forReadingFrom: URL(fileURLWithPath: "/usr/bin/yes")).bytes.lines {
print("b")
}
}
When I did this (10 seconds rather than 2 seconds, and in an app rather than a Playground), I do see them jumping back and forth.
Admittedly, it was not one-for-one interleaving (it was lots of “a”s followed by lots of “b”s, and then the process repeats). But there is no reason it would interleave perfectly one-for-one between the two processes, because while lines emits an asynchronous sequence of lines, behind the scenes it is likely reading chunks of output from the pipe, not really consuming it line by line, which would be very inefficient. (And, IMHO, it’s interesting that the URLSession behavior is different, but not terribly surprising.) And you effectively have two processes racing, so there is no reason to expect a graceful, alternating, behavior between the two.
If you replace yes with a program that waits a little between lines of output (e.g., I had it wait for 0.01 seconds between each line of output), then you will see it interleave a bit more frequently. Or when I added an actor to keep track which process last emitted a line of output, that was enough to trigger an immediate back-and-forth processing of one line from each yes output alternatively.
You might also want to consider the implication of running these two loops with Task { ... }, as that will run each “operation asynchronously as part of a new top-level task on behalf of the current actor” [emphasis added]. You might consider detached tasks or separate actors (to reduce contention on the current actor handling both loops). In my tests, it did not change the results too dramatically, but your mileage may vary. Regardless, it is something to be aware of.

Synchronize nested async network requests inside a while loop by using Semaphores

I have a func that gets a list of Players. When i fetch the players i need only to show those who belongs to the current Team so i am showing only a subset of the original list by filtering them. I don't know in advance, before making the request, how much players belong to the Team selected by the User, so i may need to do additional requests until i can display on the TableView at least 10 rows of Players. The User by pulling up from the bottom of the TableView can request more players to display. To do this i am calling a first async func request which in turn calls, inside a while, another nested async func request. Here a code to give you an idea of what i am trying to do:
let semaphore = DispatchSemaphore(value: 0)
func getTeamPlayersRequest() {
service.getTeamPlayers(...)
{
(result) in
switch result
{
case .success(let playersModel):
if let validCurrentPage = currentPageTmp ,
let validTotalPages = totalPagesTmp ,
let validNextPage = self.getTeamPlayersListNextPage()
{
while self.playersToShowTemp.count < 10 && self.currentPage < validTotalPages
{
self.currentPage = validNextPage //global var
self.fetchMorePlayers()
self.semaphore.wait() //global semaphore
}
}
case .failure(let error):
//some code...
}
})
}
private func fetchMorePlayers(){
// Completion handler of the following function is never called..
service.getTeamPlayers(requestedPage: currentPage, completion: {
(result) in
switch result
{
case .success(let playersModel):
if let validPlayerList = playersList,
let validPlayerListData = validPlayerList.data,
let validTeamModel = self.teamPlayerModel,
let validNextPage = self.getTeamPlayersListNextPage()
{
for player in validPlayerListData
{
if ( validTeamModel.id == player.team?.id)
{
self.playersToShowTemp.append(player)
}
}
}
self.currentPage = validNextPage
self.semaphore.signal() //global semaphore
case .failure(let error):
//some code...
}
}
}
I have tried both with DispatchGroup and Semaphore but i don't get it what i am doing wrong. I debugged the code and saw that the first async call get executed in a different queue (not the main queue) and a different thread. The nested async call getexecuted on a different thread but i don't know if it's the same concurrent queue of the first async call.
The completion handler of thenested call it's never called. Does anyone know why? is the self.semaphore.wait(), even if it get executed after the fetchMorePlayers() return, blocking/preventing the nested async completion handler to be called?
I am noticing through the Debugger that the completion() in the Xcode vars window has the note "swift partial apply forwarder for closure #1"
If we inline the function call in your loop, it looks something like this:
while self.playersToShowTemp.count < 10 && self.currentPage < validTotalPages
{
self.currentPage = validNextPage //global var
nbaService.getTeamPlayers(requestedPage: currentPage, completion: { ... })
self.semaphore.wait() //global semaphore
}
So nbaService.getTeamPlayers schedules a request, probably on the main DispatchQueue and immediately returns. Then you call wait on your semaphore, which blocks, probably before GCD even tries to run the task scheduled by nbaService.getTeamPlayers.
That's a problem on DispatchQueue.main, which is a serial queue. It has to be a serial queue for UI updates to work. What normally happens is on some iteration of the run loop you make a request, and return.. that bubbles back up to the run loop, which checks for more events and queued tasks. In this case, when your completion handler in getTeamPlayersRequest is waiting to be run, the run loop (via GCD) executes it for that iteration. Then you block the main thread, so the run loop can't continue. If you do need to block always do it on a different DispatchQueue, preferably a .concurrent one.
There is sometimes confusion about what .async does. It only means "run this later and right now return control back to the caller". That's all. It does not guarantee that your closure will run concurrently. It merely schedules it to be run later (possibly soon) on whatever DispatchQueue you called it on. If that queue is a serial queue, then it will be queued to run in its turn in that dispatch queue's run loop. If it's a concurrent queue (ie one you specifically set the attributes to include .concurrent). Then it will run, possibly at the same time as other tasks on that same DispatchQueue.
To avoid that instead of using a loop you can use async-chaining.
private func fetchMorePlayers(while condition: #autoclosure #escaping () -> Bool){
guard condition() else { return }
nbaService.getTeamPlayers(requestedPage: currentPage, completion: {
(result) in
switch result
{
case .success(let playersModel):
if let validPlayerList = playersList,
let validPlayerListData = validPlayerList.data,
let validTeamModel = self.teamPlayerModel,
let validNextPage = self.getTeamPlayersListNextPage()
{
for player in validPlayerListData
{
if ( validTeamModel.id == player.team?.id)
{
self.playersToShowTemp.append(player)
}
}
}
self.currentPage = validNextPage
// Chain to next call
self.fetchMorePlayers(while: condition))
case .failure(let error):
//some code...
}
}
}
Then in getTeamPlayersRequest you can do this:
func getTeamPlayersRequest() {
service.getTeamPlayers(...)
{
(result) in
switch result
{
case .success(let playersModel):
if let validCurrentPage = currentPageTmp ,
let validTotalPages = totalPagesTmp ,
let validNextPage = self.getTeamPlayersListNextPage()
{
self.currentPage = validNextPage //global var
self.fetchMorePlayers(while: self.playersToShowTemp.count < 10 && self.currentPage < validTotalPages)
}
case .failure(let error):
//some code...
}
})
}
This avoids the need to block on a semaphore, because each subsequent request happens in the completion handler of the previously completed one. The only issue is if you need for the completion handler in getTeamPlayersRequest to block while the fetchMorePlayers requests are being fetched, because now it won't you can re-introduce the semaphore. In that case the guard statement in fetchMorePlayers becomes:
guard condition() else
{
self.semaphore.signal()
return
}
That way it only signals on the last completion handler in the chain. You may need to block in a different DispatchQueue though. I think if you need to block, you probably have something about your design that needs to be reconsidered.
If you find yourself reaching for semaphores, it is almost always a mistake. Semaphores are inefficient at best, and introduce deadlock risks if misused. Semaphores should generally be avoided. (Don't get me wrong: Semaphores can be useful in some very narrow use cases, but this is not one of them.)
Use asynchronous patterns. One simple approach might be to recursively call the routine, calling the completion handler when done:
func startFetching(#escaping completion: () -> Void) {
fetchPlayers(page: 0, completion: completion)
}
private func fetchPlayers(page: Int, #escaping completion: () -> Void) {
// prepare request
// now perform request
performRequest(...) { ...
if let error = error {
completion()
return
}
...
if doesNeedMorePlayers {
fetchPlayers(page: page + 1, completion: completion)
} else {
completion()
}
}
}
Personally, I might probably add another closure to emit the players retrieved as we go along, e.g. like, if not actually, a Combine Publisher. Or if you want to update the UI all at once at the very end, just pass the players retrieved thus far as additional parameter in this recursive routine and pass the whole array back in the completion handler. But avoid globals or other state properties.
But the broader idea is to scrupulously avoid semaphores and instead embrace asynchronous patterns.

How to have a timeout when using DispatchGroup?

We can use a DispatchGroup in swift to add many tasks to the group. The group will wait until all the tasks are complete before proceeding to the next codes.
let dg = DispatchGroup()
dg.notify(queue: .global()) {
// run code here on completion
}
Is there a way to add a timeout for this dispatchGroup in case the tasks are taking too long to complete?
[Edit]
I am aware that DispatchGroup.wait(timeout:) adds a timeout. But this makes it synchronously wait. Is there a way that it is asynchronous using the notify method, but still have a timeout?
If you want the DispatchGroup to have a timeout asynchronously try:
var dg: DispatchGroup? = DispatchGroup()
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.dg = nil // after time out group is removed asynchronously
}
dg?.notify(queue: .global()) {
// run code here on completion
}
Just to add an alternative way; to easily reuse this in multiple parts of your code, you could provide a DispatchGroup extension like this:
extension DispatchGroup {
func notify(queue: DispatchQueue, timeout: TimeInterval, execute work: #escaping () -> ()) {
var selfDestructingCompletion: (() -> ())?
selfDestructingCompletion = {
selfDestructingCompletion = nil
work()
}
self.notify(queue: queue) {
selfDestructingCompletion?()
}
queue.asyncAfter(deadline: .now() + timeout) {
selfDestructingCompletion?()
}
}
}
Which then you would just use like this:
// 1. something you need to have for the dispatch group anyway
let someSerialQueue = DispatchQueue(label: "someQueue")
let group = DispatchGroup()
for someApi in apiList {
group.enter()
someApi.call() {
group.leave()
}
}
// 2. the new method, just with the timeout parameter
group.notify(queue: someSerialQueue, timeout: 5.0) {
// Run the completion for completing the tasks or for timeout in one place
}
This has the added benefits of:
having no thread issues as long as you pass a serial queue (not true for the accepted answer)
calling the completion only in one place when it's used
Just be sure to prefix the name of the method to some specific namespace of yours if you plan to insert this in a public library, to avoid clashes with other similar implementations of this extension.

Swift: Simple DispatchQueue does not run & notify correctly

What i am doing wrong? At playground it runs as it should. But as soon as i deploy it on iOS simulator it returns the wrong sequence.
#objc func buttonTapped(){
let group = DispatchGroup()
let dispatchQueue = DispatchQueue.global(qos: .default)
for i in 1...4 {
group.enter()
dispatchQueue.async {
print("🔹 \(i)")
}
group.leave()
}
for i in 1...4 {
group.enter()
dispatchQueue.async {
print("❌ \(i)")
}
group.leave()
}
group.notify(queue: DispatchQueue.main) {
print("jobs done by group")
}
}
Console Output:
I don't get it. 😅
You should put the group.leave() statement in the dispatchQueue.async block as well, otherwise it will be executed synchronously before the async block would finish execution.
#objc func buttonTapped(){
let group = DispatchGroup()
let dispatchQueue = DispatchQueue.global(qos: .default)
for i in 1...4 {
group.enter()
dispatchQueue.async {
print("🔹 \(i)")
group.leave()
}
}
for i in 1...4 {
group.enter()
dispatchQueue.async {
print("❌ \(i)")
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print("jobs done by group")
}
}
As Dávid said, properly employed dispatch groups only ensure that the notification takes place after all of the tasks finish, which you can achieve by calling leave from within the dispatched blocks, as he showed you. Or alternatively, since your dispatched tasks are, themselves, synchronous, you don't have to manually enter and leave the group, but can use group parameter of async method:
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .default)
for i in 1...4 {
queue.async(group: group) {
print("🔹 \(i)")
}
}
for i in 1...4 {
queue.async(group: group) {
print("❌ \(i)")
}
}
group.notify(queue: .main) {
print("jobs done by group")
}
Use group.enter() and group.leave() when calling some asynchronous method, but in the case of these print statements, you can just use async(group:execute:) as shown above.
Now, we've solved the problem where the "jobs done by group" block didn't wait for all of the dispatched tasks. But, because you're doing all of this dispatching to a concurrent queue (all the global queues are concurrent queues), you have no assurances that your tasks will be performed in the order that you requested. They're queued up in a strict FIFO manner, but because they're concurrent, you have no assurances when you'll hit the respective print statements.
If you need it to print the messages in order, you will have to use a serial queue. For example, if you create your own queue, in the absence of the .concurrent attribute, the following will create a serial queue:
// create serial queue
let queue = DispatchQueue(label: "...")
// but not your own concurrent queue:
//
// let queue = DispatchQueue(label: "...", attributes: .concurrent)
//
// nor one of the global concurrent queues:
//
// let queue = DispatchQueue.global(qos: .default)
//
And if you run the above code with this serial queue, you'll see what you were looking for:
🔹 1
🔹 2
🔹 3
🔹 4
❌ 1
❌ 2
❌ 3
❌ 4
jobs done by group
But, then, again, if you were using a serial queue, the group would be completely unnecessary (you could just add the "completion" task as yet another dispatched task at the end of the serial queue). I only show the use of serial queues as a way to avoid the race condition of dispatching eight tasks to a concurrent queue.

How to stop DispatchGroup or OperationQueue waiting?

DispatchGroup and OperationQueue have methods wait() and waitUntilAllOperationsAreFinished() which wait for all operations in respective queues to complete.
But even when I call cancelAllOperations it just changes the flag isCancelled in every running operation and stop the queue from executing new operations. But it still waits for the operations to complete. Therefore running the operations must be stopped from the inside. But it is possible only if operation is incremental or has an inner cycle of any kind. When it's just long external request (web request for example), there is no use of isCancelled variable.
Is there any way of stopping the OperationQueue or DispatchGroup waiting for the operations to complete if one of the operations decides that all queue is now outdated?
The practical case is: mapping a request to a list of responders, and it is known that only one may answer. If it happens, queue should stop waiting for other operations to finish and unlock the thread.
Edit: DispatchGroup and OperationQueue usage is not obligatory, these are just tools I thought would fit.
OK, so I think I came up with something. Results are stable, I've just tested. The answer is just one semaphore :)
let semaphore = DispatchSemaphore(value: 0)
let group = DispatchGroup()
let queue = DispatchQueue(label: "map-reduce", qos: .userInitiated, attributes: .concurrent)
let stopAtFirst = true // false for all results to be appended into one array
let values: [U] = <some input values>
let mapper: (U) throws -> T? = <closure>
var result: [T?] = []
for value in values {
queue.async(group: group) {
do {
let res = try mapper(value)
// appending must always be thread-safe, otherwise you end up with race condition and unstable results
DispatchQueue.global().sync {
result.append(res)
}
if stopAtFirst && res != nil {
semaphore.signal()
}
} catch let error {
print("Could not map value \"\(value)\" to mapper \(mapper): \(error)")
}
}
}
group.notify(queue: queue) { // this must be declared exactly after submitting all tasks, otherwise notification fires instantly
semaphore.signal()
}
if semaphore.wait(timeout: .init(secondsFromNow: 5)) == .timedOut {
print("MapReduce timed out on values \(values)")
}