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...
Related
Look at these two slightly similar cases:
case 1:
func test() {
DispatchQueue.global().sync {
print("a")
DispatchQueue.main.sync {
print("b")
}
}
}
and
case 2:
func test() {
DispatchQueue.main.async {
print("a")
DispatchQueue.main.sync {
print("b")
}
}
}
In the first case the code crashes with
EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP,...
the explaination for that acc to me is that in the first case the task is submitted synchronoulsy on the global queue using DispatchQueue.global().sync . Given the the function test() itself was called from the main queue, the main queue will wait for the task added in *DispatchQueue.global().sync to finish. However, it encounter another task added synchronously to the main queue DispatchQueue.main.sync { print("b") } . Now the task that's running on the main queue is blocked waiting for this new task that's again running on the main queue to complete. Hence, deadlock which causes the crash.
In the second case, it is somewhat similar to the first, However, there is no crash even though the task print("b") is added to main serial queue synchronoulsy (which is inside test() method which is itself called from the main queue) but it is still causing deadlock which is evident from the fact that "a" is printed but "b" is not printed in the second case.
I need to understand why there was no crash in this case (on playground)
If you jump to the top of that stack trace resulting from that EXC_BAD_INSTRUCTION and it will tell you precisely what the issue was:
Why your second example did not generate a EXC_BAD_INSTRUCTION, I cannot say, as it does on my computer.
That having been said, most deadlocks simply do not generate a nice EXC_BAD_INSTRUCTION for you. Often it just freezes. Why your second was not caught by the same diagnostic is unclear. But you should not be relying upon this EXC_BAD_INSTRUCTION, anyway, as it only catches one particular deadlock risk...
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.
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.
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
The following code does not print ..In Background.. and prints ..Executing..
I know I am missing something really basic. I read up other questions with the same, but they all seems like they are running the same code.
Some possible duplicates that did not help here
I have extracted the exact code in question, I don't think anything more is needed to help debug this.
func execute() {
print("..Executing..")
DispatchQueue.global(qos: .background).async {
self.doInBackground()
DispatchQueue.main.async {
self.doAfterBackgroundCompletes()
}
}
}
func doInBackground() {
print("..In Background..")
EDIT
The below works. But I am wondering shouldn't a http request be in a background thread. Atleast in Android if http request is not in the background thread it causes an issue.
func execute() {
print("..Executing..")
self.doInBackground()
self.doAfterBackgroundCompletes()
// DispatchQueue.global(qos: .background).async {
// DispatchQueue.main.async {
// self.doInBackground()
// self.doAfterBackgroundCompletes()
// }
// }
}
I've seen the same behaviour in Swift 4. While I can't explain the it, changing the QoS from .background to .default did execute and that solved my issue. I know it's not a solution as such but I hope it's a reasonable work-around for you.
Maybe I am missing something?
I just ran the following code:
func execute() {
print("..Executing..")
DispatchQueue.global(qos: .background).async {
self.doInBackground()
DispatchQueue.main.async {
self.doAfterBackgroundCompletes()
}
}
}
func doInBackground() {
print("..In Background..")
}
func doAfterBackgroundCompletes() {
print("..COMPLETED..")
}
And the results are:
..Executing..
..In Background..
..COMPLETED..
From where are you calling the execute function?
Here is working code.
DispatchQueue.global().async(execute: {
print("global...")
DispatchQueue.main.sync{
print("main...")
}
})
I had this problem too and solved it by creating a custom dispatch queue, though I believe it could be mitigated (if not solved outright) by increasing the task priority.
I believe the issue was that the global dispatch queue is used by many things, and the global queue has a limited number of concurrent tasks. By adding tasks into the .background queue your item will only run if there are no .utility or higher priority tasks left.
This becomes a problem when for some reason either a lot higher priority tasks are being queued, or if the ones that are happen to be taking an inordinate amount of time. This can be a problem in any queue, though the global queue is probably much more likely to have this issue since more things will use it.
This is from Apple's docs, under the Creating and Managing Dispatch Queues heading:
The actual number of tasks executed by a concurrent queue at any given moment is variable and can change dynamically as conditions in your application change. Many factors affect the number of tasks executed by the concurrent queues, including the number of available cores, the amount of work being done by other processes, and the number and priority of tasks in other serial dispatch queues.
...
As you might expect, tasks in the high-priority concurrent queue execute before those in the default and low-priority queues. Similarly, tasks in the default queue execute before those in the low-priority queue.