Swift Async print order? - ios

Does this always print in the order of 1 5 2 4 3?
print("1")
DispatchQueue.main.async {
print("2")
DispatchQueue.main.async {
print(3)
}
print("4")
}
print("5")
I feel the answer is no, but I cannot explain it and hope someone could clarify my understanding. Thank you!

It depends on which thread you start the operation.
If you start from the main then, then you get 1, 5, 2, 4, 3
If you start from a background thread, then most of the time you'll get them same result (1, 5, 2, 4, 3), however this is not guaranteed as the background thread can be put to sleep at any time by the OS, and if this happens right before the print(5)call, then 5 will be the last to be printed.
Just a note that if the code from the question is the only one in your app/playground, then you might be surprised by running into partial prints, as the app/playground exits as soon as it hits the print(5) line, before having a chance to execute the async dispatch. To circumvent this, you can make sure that RunLoop.current.run() gets executed on the main thread as the last part of your code.
Here are some diagram that try to illustrate what happens in the main-thread-only scenario, and the one where a background thread is involved:

You will always get 1, 2, 4, 3. The 5 will always be after the 1. But where it ends up in relation to the others depends on what queue the whole thing started on.
If this is started from the main queue then 5 will always be between 1 and 2.
Here's why:
This code starts on the main queue. 1 is printed. You then enqueue another block to run asynchronously on the main queue so that block will be run after the current block completes and the main queue gets to the end of the current run loop. The code continues to the next line which is to print 5. The current block ends and the next block on the main queue is run. This is the block of the first call to DispatchQueue.main.async. As this block runs it prints 2 (so now we have 1 5 2). Another block is enqueued to the main queue just like the last one. The current block continues and prints 4 (so now we have 1 5 2 4). The block ends and the next block on the main queue is run. This is the final block we added. That block runs and it prints 3 giving the final output of 1 5 2 4 3.
If you started on some background queue then 5 can appear anywhere after 1 but for such simple code, the 5 will most likely still appear between 1 and 2 but it can appear anywhere after the 1 in a general case.
Here's why:
This code starts on a background queue. 1 is printed. You then enqueue another block to run asynchronously on the main queue so that block will be run after the current main queue run loop completes. The code continues to the next line which is to print 5. The current block ends. The block added to the main queue is run in parallel to the background queue. Depending on the time of when the block added to the main queue is run in relation to the remaining code on the background queue is run, the output of print("5") can be intermingled with the prints from the main queue. This is why the 5 can appear anywhere after the 1 when started from the background.
But the simple code in the question will likely always give 1 5 2 4 3 even when started on the background because the code is so short and takes so little time.
Here's some code that puts the 5 elsewhere:
func asyncTest() {
print("1")
DispatchQueue.main.async {
print("2")
DispatchQueue.main.async {
print(3)
}
print("4")
}
for _ in 0...1000 {
}
print("5")
}
DispatchQueue.global(qos: .background).async {
asyncTest()
}
The existence of the loop causes the 5 to take a little longer before it appears which allows the main queue to get executed some before 5 is printed. Without the loop, the background thread executes too quickly so the 5 appears before 2.
If running this test in a playground, add:
PlaygroundPage.current.needsIndefiniteExecution = true
to the top of the playground (just after the imports). You will also need:
import PlaygroundSupport

Related

Why does DispatchGroup interfere with main queue?

I have to stop my code for one second in order to get server databases synced before continuing.
All code snippets below are run from main thread.
I used this first:
// code 1
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
// dummy
}
// stuff
Outcome obviously not as desired since stuff is executed immediately. I'd like stuff to run delayed after the code block (not nice, but there's a reason for it).
// code 2
let group = DispatchGroup()
group.enter()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
group.leave()
}
group.wait()
// stuff
Deadlock!
Questions 1 : Why is main queue without DispatchGroup working and locks in conjunction with DispatchGroup?
// code 3
let group = DispatchGroup()
group.enter()
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
group.leave()
}
group.wait()
// stuff
This works as desired!
Question 2 : Why is global queue required with DispatchGroup to make stuff run delayed?
I read this here:
https://stackoverflow.com/a/42773392/3657691/ https://stackoverflow.com/a/28821805/3657691/ https://stackoverflow.com/a/49287115/3657691/
I am going to assume that you are running these snippets on the main thread, as this is most probably the case from the issue description.
Dispatch queues are basically task queues, so a queue has some tasks enqueued. Let's see what is on the main queue when you are executing the snippet generating a deadlock.
The main queue has a task executing (the one executing the snippet)
Then you call asyncAfter which will enqueue another task (the closure containing group.leave()) on the main queue, after the specified deadline.
Now the task being executed (1.) is being blocked by the call to group.wait(), and it's going to block the whole main queue until it finishes execution. This means the enqueued task (2.) will have to wait until the first one finishes. You can see here that the 2 tasks will block each other:
the first one (1.) will wait until the second one (2.) releases the dispatch group
the second one (2.) will wait until the first one (1.) finishes execution so it can be scheduled
For question number 2, using a global queue (or literally any other queue other than the one our current code is being executed on - in this example the main queue), will not block the asyncAfter task (obviously, because it's being scheduled on another queue which is not blocked, thus it gets the chance to be executed).
This is true for serial dispatch queues (the main queue being a serial queue as well). Serial dispatch queues will execute their tasks serially, that is only one at a time.
On the other hand, for a concurrent dispatch queue, this scenario won't result in a deadlock, because the asyncAfter task won't be blocked by the waiting task. That's because concurrent dispatch queues don't wait for the task being executed to be finished to schedule the next enqueued task.
This would even be a good exercise to try to run this scenario on a serial queue, and then on a concurrent queue to observe the differences

iOS RunLoop and DispatchQueue.main.async

Why does the print("2") part never get called in the following code?
I'd think that the inner main.async would push the block into the main loop's queue,
and then RunLoop.run would execute it, but apparently that isn't what happens.
(It prints 1, run, run, run, etc.)
Also, if I remove the outer main.async, and just directly run the code in that block
(still on the main queue, in viewDidLoad of a new single-view app),
then the inner main.async block does get executed (prints 1, run, 2).
Why does this change make such a difference?
var x = -1
DispatchQueue.main.async { // comment out this line for question #2
print("1")
x = 1
DispatchQueue.main.async {
print("2")
x = 2
}
while x == 1 {
print("run")
RunLoop.main.run(mode: .default, before: Date() + 1)
}
} // comment out this line for question #2
In your first example, the first async blocks the main serial queue until it returns from that outer async call, something that won’t happen while x is 1. That inner GCD async task (that updates x to 2) will never have a chance to run since that serial GCD queue is now blocked in that while loop. The attempt to run on the main run loop does not circumvent the rules/behavior of GCD serial queues. It only drains the run loop of events that have been added to it.
In your second example, you haven’t blocked the GCD main queue, so when you hit run, the dispatched block that updates x to 2 does have a chance to run, letting it proceed.
Bottom line, don’t conflate the GCD main queue and the main run loop. Yes, they both use the main thread, but run loops can’t be used to circumvent the behavior of serial GCD queues.

How does wait succeed for a block that is to be executed on the next dispatch?

import XCTest
#testable import TestWait
class TestWait: XCTestCase {
func testX() {
guard Thread.isMainThread else {
fatalError()
}
let exp = expectation(description: "x")
DispatchQueue.main.async {
print("block execution")
exp.fulfill()
}
print("before wait")
wait(for: [exp], timeout: 2)
print("after wait")
}
}
Output:
before wait
block execution
after wait
I'm trying to rationalize the sequence of the prints. This is what I think:
the test is ran on main thread
it dispatches a block off the main thread, but since the dispatch happens from the main thread, then the block execution has to wait till the current block is executed
"before wait" is printed
we wait for the expectation to get fulfilled. This wait sleeps the current thread, ie the main thread for 2 seconds.
So how in the world does wait succeed even though we still haven't dispatched off of main thread. I mean "after wait" isn't printed yet! So we must still be on main thread. Hence the "block execution" never has a chance to happen.
What is wrong with my explanation? I'm guessing I it must be something with how wait is implemented
The wait(for:timeout:) of XCTestCase is not like the GCD group/semaphore wait functions with which you are likely acquainted.
When you call wait(for:timeout:), much like the GCD wait calls, it will not return until the timeout expires or the expectations are resolved. But, in the case of XCTestCase and unlike the GCD variations, inside wait(for:timeout:), it is looping, repeatedly calling run(mode:before:) until the expectations are resolved or it times out. That means that although testX will not proceed until the wait is satisfied, the calls to run(mode:before:) will allow the run loop to continue to process events (including anything dispatched to that queue, including the completion handler closure). Hence no deadlock.
Probably needless to say, this is a feature of XCTestCase but is not a pattern to employ in your own code.
Regardless, for more information about how Run Loops work, see the Threading Programming Guide: Run Loops.
When in doubt, look at the source code!
https://github.com/apple/swift-corelibs-xctest/blob/ab1677255f187ad6eba20f54fc4cf425ff7399d7/Sources/XCTest/Public/Asynchronous/XCTWaiter.swift#L358
The whole waiting code is not simple but the actual wait boils down to:
_ = runLoop.run(mode: .default, before: Date(timeIntervalSinceNow: timeIntervalToRun))
You shouldn't think about waits in terms of threads but in terms of queues. By RunLoop.current.run() you basically tell the current code to start executing other items in the queue.
The wait function utilizes NSRunLoop inside most likely. The run loop doesn't block the main thread like sleep functions do. Despite execution of the function testX does not move on. The run loop still accepts events scheduled at the thread and dispatches them to be executed.
UPDATE:
This is how I envision the work of a run loop. In a pseudocode:
while (currentDate < dateToStopRunning && someConditionIsTrue())
{
if (hasEventToDispatch()) //has scheduled block?
{
runTheEvent();// yes, we have a block, so we run it!
}
}
The block that you put for async execution is checked inside hasEventToDispatch() method and is executed. It fullfils the expectation which is checked at the next iteration of the while loop in someConditionIsTrue() so the while loop exits. testX continues exection and after wait is printed

What happens if dispatch on same queue?

I'd like to understand for below case if it's needed to check whether callbackQueue is current queue.
Please help me clear these scenarios, about what could happen if current queue is callback queue:
callbackQueue is main queue.
callbackQueue is concurrent queue.
callbackQueue is serial queue.
- (void)fetchWithCallbackQueue:(dispatch_queue_t)callbackQueue
{
dispatch_async(callbackQueue, ^{
});
}
I highly recommend you to watch these videos. Then go through the examples I provided and then change the code and play around with them as much as you can. It took me 3 years to feel fully comfortable with iOS multi-threading so take your time :D
Watch the first 3 minutes of this RWDevCon video and more if you like.
Also watch 3:45 until 6:15. Though I recommend you watch this video in its entirety.
To summarize the points the videos make in the duration I mentioned:
threading and conccurrency is all about the source queue and destination. queue.
sync vs. async is specifically a matter of the source queue.
Think of source and destination queues of a highway where your work is being done.
If you do async, then it's like you sending a car (has to deliver stuff) exiting the highway and then continue to let other cars drive in the highway.
If you do sync, then it's like you sending a car (has to deliver stuff) exiting the highway and then halting all other cars on the highway until the car delivers all its stuff.
Think of a car delivering stuff as a block of code, starting and finishing execution.
What happens for main queue is identical to what happens for serial queue. They're both serial queues.
So if you're already on main thread and dispatch to main thread and dispatch asynchronously then, anything you dispatch will go to the end of the queue
To show you what I mean: In what order do you think this would print? You can easily test this in Playground:
DispatchQueue.main.async {
print("1")
print("2")
print("3")
DispatchQueue.main.async {
DispatchQueue.main.async {
print("4")
}
print("5")
print("6")
print("7")
}
print("8")
}
DispatchQueue.main.async {
print("9")
print("10")
}
It will print:
1
2
3
8
9
10
5
6
7
4
Why?
It's mainly because every time you dispatch to main from main, the block will be placed at the end of the main queue.
Dispatching to main while you're already on the main queue is very hidden subtle reason for many tiny delays that you see in an app's user-interaction.
What happens if you dispatch to the same serial queue using sync?
Deadlock! See here
If you dispatch to the same concurrent queue using sync, then you won't have a deadlock. But every other thread would just wait the moment you do sync. I've discussed that below.
Now if you're trying to dispatch to a concurrent queue, then if you do sync, it's just like the example of the highway, where the entire 5 lane highway is blocked till the car delivers everything. But it's kinda useless to do sync on a concurrent queue, unless you're doing something like a .barrier queue and are trying to solve a read-write problem.
But to just see what happens if you do sync on a concurrent queue:
let queue = DispatchQueue(label: "aConcurrentQueue", attributes: .concurrent)
for i in 0...4 {
if i == 3 {
queue.sync {
someOperation(iteration: UInt32(i))
}
} else {
queue.async {
someOperation(iteration: UInt32(i))
}
}
}
func someOperation(iteration: UInt32) {
sleep(1)
print("iteration", iteration)
}
will log:
'3' will USUALLY (not always) be first (or closer to the first), because sync blocks get executed on the source queue. As docs on sync say:
As a performance optimization, this function executes blocks on the current thread whenever possible
The other iterations happen concurrently. Each time you run the app, the sequence may be different. That's the inherit unpredictability associated with concurrency. 4 will be closer to being completed last and 0 would be closer to being finished sooner. So something like this:
iteration 3
iteration 0
iteration 2
iteration 1
iteration 4
If you do async on a concurrent queue, then assuming you have a limited number of concurrent threads, e.g. 5 then 5 tasks would get executed at once. Just that each given task is going to the end of the queue. It would make sense to do this for logging stuff. You can have multiple log threads. One thread logging location events, another logging purchases, etc.
A good playground example would be:
let queue = DispatchQueue(label: "serial", attributes: .concurrent)
func delay(seconds: UInt32 ) {
queue.async {
sleep(seconds)
print(seconds)
}
}
for i in (1...5).reversed() {
delay(seconds: UInt32(i))
}
Even though you've dispatched the 5 first, this would print
1
2
3
4
5
In your example with dispatch_async (or just async in Swift), it doesn’t matter. The dispatched block will simply be added to the end of the relevant queue and will run asynchronously whenever that queue becomes available.
If, however, you used dispatch_sync (aka sync in Swift), then suddenly problems are introduced if you dispatch from a serial queue back to itself. With a “synchronous” dispatch from a serial queue to itself, the code will will “deadlock”. (And because the main queue is a serial queue, synchronous dispatches from main queue to itself manifest the same problem.) The dispatch_sync says “block the current thread until the designated queue finishes running this dispatched code”, so obviously if any serial queue dispatches synchronously back to itself, it cannot proceed because it’s blocking the queue to which you’ve dispatched the code to run.
Note that any blocking GCD API, such as dispatch_semaphore_wait and dispatch_group_wait (both known as simply wait in Swift), will suffer this same problem as the synchronous dispatch if you wait on the same thread that the serial queue uses.
But, in your case, dispatching asynchronously with dispatch_async, you shouldn’t have any problems.

global_queue with qos_class_user_interactive

I try to understand GCD and wrote this code to find out run priority:
override func viewDidLoad() {
super.viewDidLoad()
fetchImage()
print(1)
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
print(2)
}
dispatch_async(dispatch_get_main_queue()) {
print(3)
}
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
print(5)
}
}
I got next result in the console:
1
2
5
3
So the question is:
Part 1: Why 3 is after 5 (main_queue has highest priority?)
Part 2: And why 2 is higher that 3 and 5 as well?
Thank you guys!
Bear in mind: this is multi-threading, on a multi-core device, writing output to a log that you don't know the thread safety and internal management of...
That said:
1 is first because it's synchronous
2 is second because it's also synchronous
3 is not next because it's pushed into the queue of things waiting to run on the main thread run loop and you don't know what else is already in that queue
5 is before 3 because it's (basically) the same priority but it's running on a queue that probably does't have anything else waiting (QOS_CLASS_USER_INTERACTIVE ~= main thread priority)
Note, I say ~= because I haven't checked the exact values and it may differ slightly though I expect the priority values to match, otherwise 'interactive' wouldn't mean much...

Resources