I have a really strange situation where I have multiple nested dispatch queues, and at some point I need to update the UI. DispatchQueue.main.async doesn't execute if I am already on the main thread, if I skip it I can see in the debugger (as well as using Thread.isMainThread) that I am on the main thread but inside one of the custom queues and the UI doesn't update.
How do I get to the “real” main thread (com.apple.main-thread)?
Or how can I ensure that a queue created via
private let progressQueue = DispatchQueue(label: "custom_thread", attributes: .concurrent)
is definitely a background thread?
You said:
DispatchQueue.main.async doesn't execute ...
If this dispatched code is not running, then the main queue must be blocked.
I see that Thread.isMainThread is true, but when updating the ui nothin happens
It does not matter that you currently are on the main thread or not, the code dispatched to main via async simply cannot run until the main queue is free again.
How do I get to the “real” main thread (com.apple.main-thread)? Or how can I ensure that a queue created via ... is definitely a background thread?
The dispatch queue that you are creating is a background queue. And GCD decides, at its discretion, on which thread dispatched code will run. Generally, code dispatched to a background queue will run on one of GCD’s worker threads of the appropriate QoS, but there are some interesting edge cases.
Specifically, if you dispatch synchronously (i.e., with sync) from the main thread to a GCD queue, as an optimization, GCD may actually run the dispatched code on the main thread. When we sync to a background queue, the main thread is blocked until the dispatched code finishes, anyway, so GCD may just use the idle main thread and avoid a costly context switch. That is why code dispatched synchronously to a background queue may report that isMainThread is still true. But, just to be clear, it is not the case that this is not the “real” main thread. It just is just code dispatched synchronously to some background queue that is actually running on the main thread (and, in your case, likely blocking the main thread in the process).
Bottom line, we do not care what thread this queue happens to be using. The fact that GCD may be using the main thread as an optimization is immaterial. Do not get distracted by isMainThread. We only care whether we have blocked the main thread anywhere.
So, you are going to have to identify where the main thread is getting blocked. Needless to say, we should never block the main thread. By avoiding the blocking of the main thread, we ensure that our UI updates can happen freely. It also provides a better user experience and ensures that the watchdog process will never kill our app.
Given your comments, the problem is likely that sync was called from the main thread. Consistent with the above observation, one simply should not call sync from the main thread. (The one exception is the very fast data synchronizations of memory accessed from multiple threads, but even that is a pattern to be avoided, if you can.)
That having been said, there are other potential blocking patterns that could cause this problem, including (a) semaphores or groups where wait called from the main thread; or (b) some spinning loop.
But in this case, sync is the likely culprit. If you find where you are initially calling sync from the main thread, and change that to adopt an asynchronous pattern, then calls to DispatchQueue.main.async will be free to update the UI.
Your assertion "DispatchQueue.main.async doesn't execute if I am already on the main thread" is not correct. When you dispatch a block of code that block is placed on a queue and executed in order, e.g. consider this code:
print ("hello #1 \(Thread.current.description) isMain=\(Thread.isMainThread)")
DispatchQueue.global().async {
print ("hello #2 \(Thread.current.description) isMain=\(Thread.isMainThread)")
DispatchQueue.main.async {
print ("hello #3 \(Thread.current.description) isMain=\(Thread.isMainThread)")
}
}
DispatchQueue.main.async {
print ("hello #4 \(Thread.current.description) isMain=\(Thread.isMainThread)")
}
let progressQueue = DispatchQueue(label:"custom_thread", attributes: .concurrent)
progressQueue.async {
print ("hello #5 \(Thread.current.description) isMain=\(Thread.isMainThread)")
}
results in console output:
hello #1 <NSThread: 0x2824c8380>{number = 1, name = main} isMain=true
hello #2 <NSThread: 0x2824f6740>{number = 3, name = (null)} isMain=false
hello #5 <NSThread: 0x2824f6740>{number = 3, name = (null)} isMain=false
hello #4 <NSThread: 0x2824c8380>{number = 1, name = main} isMain=true
hello #3 <NSThread: 0x2824c8380>{number = 1, name = main} isMain=true
Keep in mind that the background queue could execute at any time. The only certainty is that hello #1 will appear first, and that hello #2 will appear before hello #3.
You can also see that progressQueue is definitely a background thread, and appears to be the same as the global dispatch queue.
Related
I'm quite confused.
Code below will cause a deadlock for sure:
// Will execute
DispatchQueue.main.async { // Block 1
// Will execute
DispatchQueue.main.sync { // Block 2
// Will not be executed
}
// Will not be executed
}
Because
After we dispatch_async on main queue, it's submits the first block to the main queue to execute
At some moment, the system decides to run block1
The .sync method blocks "thread / queue?" <- My question
Because the "thread / queue" is blocked, block 2 can't execute before block 1 finishes, because Main queue is a serial queue, it execute task serially, one can't execute before another finishes
My question is: Does sync block current thread it's executing on or current queue? (I understand the difference between thread & queue)
Most answers on the internet says it blocks thread
If block thread -> How come the sync { } block can still execute since the thread is blocked?
If block queue -> Make more sense? Since the queue is blocked, we can't execute one before other finishes
I found some discussions about this:
dispatch_sync inside dispatch_sync causes deadlock
Difference Between DispatchQueue.sync vs DispatchQueue.async
What happens if dispatch on same queue?
You asked:
My question is: Does sync block current thread it's executing on or current queue?
It blocks the current thread.
When dealing with a serial queue (such as the main queue), if that queue is running something whose thread is blocked, that prevents anything else from running on that queue until the queue is free again. A serial queue can only use one thread at a time. Thus, dispatching synchronously from any serial queue to itself will result in a deadlock.
But, sync does not technically block the queue. It blocks the current thread. Notably, when dealing with a concurrent queue (such as a global queue or a custom concurrent queue), that queue can avail itself of multiple worker threads at the same time. So just because one worker thread is blocked, it will not prevent the concurrent queue from running another dispatched item on another, unblocked, worker thread. Thus, dispatching synchronously from a concurrent queue to itself will not generally deadlock (as long as you don’t exhaust the very limited worker thread pool).
E.g.
let serialQueue = DispatchQueue(label: "serial")
serialQueue.async {
serialQueue.sync {
// will never get here; deadlock
print("never get here")
}
// will never get here either, because of the above deadlock
}
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
concurrentQueue.async {
concurrentQueue.sync {
// will get here as long as you don't exhaust the 64 worker threads in the relevant QoS thread pool
print("ok")
}
// will get here
}
You asked:
If block thread -> How come the sync { } block can still execute since the thread is blocked?
As you have pointed out in your own code snippet, the sync block does not execute (in the serial queue scenario).
so i know we should never call dispatch_sync on main queue, as its a serial queue.
so the below code will crash in xcode:
DispatchQueue.main.sync {
print("hello world")
}
but i am not able to understand 100% why it is going to crash or deadlock ?
can someone explain with some drawing because i don't have 100% proof explanation that why it is going to crash or deadlock.
i know main queue is serial queue. so it executes tasks one by one. suppose we have task A running on main queue and if i add another taskB on main queue then task B only starts after taskA finishes.
and i also know that main queue should only perform UI specific tasks.
but its just i don't have 100% proof explanation of why above code will crash / deadlock.
can someone explain with some kind of simple drawing.
i know main queue is serial queue. so it executes tasks one by one. suppose we have task A running on main queue and if i add another taskB on main queue then task B only starts after taskA finishes.
Correct
and i also know that main queue should only perform UI specific tasks.
Not correct. The UI must only be updated on the main queue but you can do whatever you want on the main queue. You can handle data on the main queue if it fits that particular application. If you're handling a lot of data and don't want to freeze the UI, then you would want to get off the main queue.
but its just i don't have 100% proof explanation of why above code will crash / deadlock.
// doing work...
DispatchQueue.main.sync { // stop the main thread and wait for the following to finish
print("hello world") // this will never execute on the main thread because we just stopped it
}
// deadlock
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.
Updating UI on a thread other than the main thread is a common mistake that can result in missed UI updates, visual defects, data corruptions, and crashes.
https://developer.apple.com/documentation/code_diagnostics/main_thread_checker
Example:
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
DispatchQueue.main.async { // Correct
self.label.text = "\(data.count) bytes downloaded"
}
}
}
task.resume()
My question starts here -
I am confused with above statement when we say .async means not simultaneously (Or not parallel) with .main. Can someone explain my problem?
DispatchQueue.main.async means you queue up a task in the main queue, without waiting the task to be executed. The main queue tasks will be run on the main thread one by one automatically, scheduled by the OS.
Think of each DispatchQueue as a worker. Calling .async adds a task under the worker's TODO list and do not wait for the worker to finish the task. DispathQueue.main is the specific worker that work on the main thread.
Oh the other hand, .sync will block the thread until the task block has finished executing. You can call .sync on any thread other than the main thread since main thread must not be blocked.
That doesn't means you cannot call DispatchQueue.main.sync. You can call DispatchQueue.main.sync just like any custom dispathQueue.sync on non- main thread.
e.g.
DispatchQueue(label: "bgqueue", qos: .background).async
{
DispatchQueue.main.sync{}
}
is OK.
But
DispatchQueue.main.async{
DispatchQueue.main.sync{}
}
is NOT.
.sync is usually not quite useful. If you want something to happen after a main queue task, you just queue that "something" into the main queue too. It is not worth to block a thread if not necessary.
That being said, here are two rules to remember when using .sync, regardless of which queue is receiving the .sync call :
never call .sync from a queue to itself, which causes deadlock.
never call .sync from main queue, which blocks the UI thread.
I think you are confused how DispatchQueue works.
DispatchQueue simply manages thread pool, and when we give it a block of code to execute it simply picks an idle thread and run that piece of code on it.
So basically one thread can be used by many queues. A queue is simply a task list which manages all the tasks which will execute in future.
So basically here when you are doing DispatchQueue.main.async then you are simply instructing main queue to execute your code without waiting for pending tasks execution.
why do this piece of code causes crash ?
DispatchQueue.main.sync {
// Operation To Perform
}
why we have to write this way :-
DispatchQueue.global().async(execute: {
print("test")
DispatchQueue.main.sync{
print("main thread")
}
})
and when we write code in CellForRowAt or any other method in which thread it goes main or global on how it works sync or async way ?
According to Apple, attempting to synchronously executing a work item on main queue results into a dead-lock.
So writing DispatchQueue.main.sync {} can lead to deadlock condition as all the UI operations performed by app is performed on main queue unless we manually switch some task on the background queue. This also answer your question regarding on which thread CellForRowAt is called. All the methods related to UI operation or UIkit are called from main thread
Performing a task synchronously means blocking a thread until the task is not completed and in this case you are attempting to block main thread on which the system / app would be already performing some task and that can lead to deadlock. Blocking main thread is not at all recommended and thats why we need to switch asynchronously to a background thread so that main thread is not blocked.
To read more you can visit the following link:
https://developer.apple.com/documentation/dispatch
Why crash In Short
DispatchQueue.main.sync {
// Operation To Perform
}
calling sync and targeting current queue is a deadlock (calling queue waits for the sync block to finish, but it does not start because target queue (same) is busy waiting for the sync call to finish) and thats probably why the crash.
For Second block : You are creating global queue and then you are getting main queue so now there is no dead lock
If you have ever used semaphore which has same issue if you don't take care
it has two methods wait and signal with wait if you block main thread then your code will never executed.
hope it is helpful
DispatchQueue.main.sync {
// Operation To Perform
}
Calling sync on a serial queue (like main) that you're already on will cause a deadlock. The first process can't finish because it's waiting for the second process to finish, which can't finish because it's waiting for the first to finish etc.
DispatchQueue.global().async(execute: {
print("test")
DispatchQueue.main.sync{
print("main thread")
}
})
Calling sync on the main thread from here works as you're moving the task to the global() queue.
There's a great 2 part GCD tutorial on raywenderlich.com which I encourage you to read https://www.raywenderlich.com/148513/grand-central-dispatch-tutorial-swift-3-part-1.