iOS RunLoop and DispatchQueue.main.async - ios

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.

Related

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.

Swift Async print order?

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

If we call main.async while on a background queue when is the code executed?

Calling main.async while on a background thread to run UI code that should be handled by the main thread appears to be a standard practise.
When we call main.async while on a background thread and the main thread is busy with normal code that's not used any GCD calls (I assume this is equivalent to main.sync?) when is this code executed?
Is the regular main.sync code executed first or will our main.async code be executed and how does this work? How can a single queue execute asynchronous and synchronous code at the same time?
Playground Example: (The A array is printed but B array isn't)
let a = "a"
let b = "b"
let aArray = [a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a]
let bArray = [b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b]
for letter in aArray {
print(letter)
}
DispatchQueue.global().async {
print("Entered background thread")
DispatchQueue.main.async {
print("Left background thread")
for letter in bArray {
print(letter)
}
}
}
There is no difference between enqueuing code on main using sync and async in term of when the code will be executed - in both cases the code will be executed as soon as it will become the first one in the main thread's queue. There is a queue of tasks that are supposed to happen on the main thread. You added something on that queue. When everything that was enqueued before your code will get executed, your code will get its turn, regardless if you added it using sync or async.
The only difference is in what happens with the calling thread - with sync the background thread becomes blocked until the code on the main thread will get executed; with async the background thread will continue.
Just a sidenote here - never call DispatchQueue.main.sync on main thread - it will cause a deadlock.

iOS dispatch_get_global_queue nested inside dispatch_get_main_queue

I've inherited a codebase that's using the following structure for threading:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
//Several AFNetworking Server calls...
})
})
I'm not very experienced with threading, so I'm trying to figure out what the possible intention behind this structure. Why grab the main queue only to access another queue immediately? Is this a common practice? For a little more context, this code is executed in an UIApplicationDidBecomeActiveNotification notification, making several necessary service calls.
Is this structure safe? Essentially my goal is to make the service calls without blocking the UI. Any help or input is appreciated.
So I think this is an interesting few lines that somebody decided to write, so let's break down what's happening here (I may be breaking things down too much, sorry in advance, it just helps my own train of thought)
dispatch_async(dispatch_get_main_queue(), dispatch_block_t block)
This will put the block as a task on the main queue (which you the code is already running in), then immediately continue executing the code in the rest of the method (If he had wanted to wait for the block task to finish before continuing, he'd have made a dispatch_sync call instead).
The main queue is serial, so it will perform these tasks exactly in this order:
go ahead and execute the block after the end of the current method (the end of the run loop for the current task)
execute any other tasks that may have been asynchronously added to the main queue before you dispatch_async your block task into the queue
execute the block task
Now block just dispatches another task to the high priority global queue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block2)
The DISPATCH_QUEUE_PRIORITY_HIGH is a concurrent queue-- so if you were to dispatch multiple tasks to this queue, it could potentially do them in parallel, depending on several system factors.
Your old co-worker wanted to make sure the networking calls in block2 were done ASAP
Because block is calling dispatch_async (which returns immediately), block task finishes, allowing the main queue to execute the next task in the queue.
The net result so far is that block2 is queued into the high priority global queue. After it executes, and your network calls complete, callback methods will be called and yadayada
...So what is the order of what's happening?
dispatch_async(dispatch_get_main_queue(), { () -> Void in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
//Several AFNetworking Server calls...
})
})
//moreCode
1) moreCode executes
2) block executes (adds block2 with network calls onto global queue)
3/4) Next task in main queue executes
4/3) Network task in global queue executes
The order of which would happen first may vary between 3 and 4, but that's concurrency for you :)
So unless old coworker wanted moreCode to execute first before adding the network calls to a global queue, you can go ahead and remove that initial dispatch_async into the main queue.
Assuming it looks like they wanted the network calls done ASAP, there probably is no reason to delay the addition of those networking tasks into a global queue.
Open to any input ^^. My experience involves reading all of the documentation on GCD today, then deciding to look at some GCD tagged questions

Resources