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.
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
This code will deadlock because:
they are in same thread
print(2) has to wait print(3)
print(3) has to wait print(2)
For example:
DispatchQueue.main.async {
print(Thread.current)
DispatchQueue.main.sync {
print(Thread.current)
print(2)
}
print(3)
}
Why in the concurrentQueue won't cause deadlock? They are also in same thread.
DispatchQueue.global().async {
print(Thread.current)
DispatchQueue.global().sync {
print(Thread.current)
print(2)
}
print(3)
}
You ask:
Why in the concurrentQueue won't cause deadlock? They are also in same thread.
No, they're necessarily in the same thread. They're in the same queue, but not necessarily the same thread. (As part of an optimization, see below, it actually might end up running on the same thread, but not necessarily so. But logically, you should think of it as running on a separate thread.)
This is the whole idea behind “concurrent queues” is that they can run individual dispatched tasks on different threads. That’s how they permit concurrent operation. One dispatched task on a concurrent queue can run on one thread while another dispatched task on that same queue can run on a separate thread.
As the old Concurrency Programming Guide says in its definition of a “concurrent queue”:
The currently executing tasks run on distinct threads that are managed by the dispatch queue.
Or, as the DispatchQueue documentation says:
DispatchQueue manages the execution of work items. Each work item submitted to a queue is processed on a pool of threads managed by the system. [emphasis added]
What makes this a little more confusing is that there is a GCD optimization that, if you are dispatching synchronously ...
As a performance optimization, this function executes blocks on the current thread whenever possible...
So, when dispatching synchronously from a queue, it actually can end up running that code on the same thread (unless you’re dispatching from a background queue to the main queue). Consider:
let queue = DispatchQueue.global()
let queue2 = DispatchQueue(label: "queue2")
queue.async {
print(1, Thread.current)
queue2.sync {
print(2, Thread.current)
}
print(3, Thread.current)
}
That second print statement will show that even though you have a completely different queue, it may, as part of the aforementioned sync optimization, run the code on the current thread. The same it true if that inner sync call was to the same queue to which the outer block was dispatched.
Now, when looking at the result of this optimization, it feels like this is just like the serial queue scenario, but it’s not. A serial queue only allows one dispatched task to run at a time, and thus the attempt to dispatch synchronously (blocking the current thread until the dispatched block runs) to itself is, by definition, a deadlock.
But a concurrent queue dispatching to itself will generally not deadlock. The one caveat is that the number of worker threads is fairly limited, so if you have “thread explosion” (where you might have more than 64 worker threads going), then it can deadlock, so make sure your limit your concurrency to some reasonable value.
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.
Example A:- This cause App Crash.
DispatchQueue.main.async {
let url = URL(string: imageUrl)
do {
let data = try Data(contentsOf: url!)
DispatchQueue.main.sync {
self.imageIcon.image = UIImage(data: data)
}
}
Example B:- But This don't
DispatchQueue.global().async {
let url = URL(string: imageUrl)
do {
let data = try Data(contentsOf: url!)
DispatchQueue.main.sync {
self.imageIcon.image = UIImage(data: data)
}
}
As per my knowledge,
x.sync means doing thing in main thread/UI thread and x.async means
doing in background thread.
Global means performing something with concurrent queue i.e Parallel
task.
Quest1:- So why does my app crashed when i performed task in background thread i.e main.async and than call main thread to update UI.
Quest2:- Is there any difference in main.async & global().async.
In simple term i come to conclusion that -
Queue- There are 3 Types of Queue i.e. 1 Main Queue, 4 Global Queue and Any No. of Custom Queues.
Threads- One is Main Thread and other background threads which system
provides to us.
DispatchQueue.main.async
-It means performing task in main queue with using of background thread (w/o blocking of UI) and when task finish it automatic Updated to UI because its already in Main Queue.
DispatchQueue.global().async along with global().sync
It means performing task in Global Queue with using of background thread and when task finish, than global().sync use bring the work from globalQueue to mainQueue which update to UI.
Reason of My App Crash
I was trying to bring the completed task to MainQueue by using(main.sync), but it was already on MainQueue because i hadnt switched the Queue, and this create DeadLock (MainQueue waiting for itself), causes my app crash
GCD
Thread -> GCD -> Operation + OperationQueue(life cycle, dependencies between different queues, cancel)
[Sync vs Async]
[iOS Thread safe]
Grand Central Dispatch GCD libdispatch operates on dispatch queues DispatchQueue with a FIFO order
DispatchQueue.<queue>.<sync/async> means run a <sync/async> task on the <queue>
GCD supports:
global queue - shared between whole iOS operation system
private queue
main - global serial queue on a main thread which is used to working with UI
DispatchQueue.main
global() - global concurrent.
DispatchQueue.global()
DispatchQueue.global(qos: .background)
Custom queue: - private serial\concurrent queue
init(label: String, qos: DispatchQoS = .unspecified, attributes: DispatchQueue.Attributes = [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil)
DispatchQueue(label: "serialQueue") // without attributes
DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
QUEUE
Usually when we talk about concurrent we talk about queues. Count of threads are depends on OS conditions. There are no run loop[About] for worker thread.
concurrent has different groups of queues with priorities(main thread, hight, default, low, background) which you pass at the task using qos QoSClass(Quality of Service) (priority GlobalQueuePriority is deprecated):
userInteractive - the highest priority. It can be used for very fast calculation which immediately are reflected on UI. For example animation calculations
userInitiated - high priority queue - relevant for UI. Up to several seconds. For example loading data for showing on UI
default - default priority queue
utility - low priority queue - up to several minutes like working with big data like images, processing...
background - background priority queue - up to several hours while app is on background like sync data.
sync/async
sync - block a current thread and wait when it will be finished on a specified queue
async - do not block a current thread and send an execution block of code to the specificified queue
Common mistake: deadlock
If you call DispatchQueue.main.sync on a main thread - the app will be frozen because the calling DispatchQueue.main.sync starts waiting immediately when the dispatched block is finished (dispatched block is not started)
Some notes:
DispatchWorkItem - delaying/cancelling/prioritise a task inside Queue or DispatchGroup
DispatchGroup if you are going to execute several async tasks with a single callback even on different queues. All these task should be grouped. DispatchGroup contains thread safe counter and when it equals 0 notify is called
//create group
let group = DispatchGroup()
//case 1
DispatchQueue.<queue>.async(group: group) //
//case 2 - manual
group.enter() //<- +1
DispatchQueue.global().async {
//logic
group.leave() //<- -1
}
//notification
group.notify(queue: <callback_queue>) {
//logic
}
Barrier flag inside concurrent queue for sync/async task guaranties that there is no race condition[About]. The best place for it is custom queue because does not block any others global tasks:
customQueue.async(flags: .barrier) {
//logic
someProperty = someValue
}
all task which were started are finished
Single Barrier task
Executing all other tasks in the queue
thread safe operation can be reached through Barrier in concurrent queue for shared variable:
read - sync operation on concurrent queue
write - async operation with barrier
[Thread safe singleton]
In first case, you run the code on main and then you use main.sync on the main thread. In essence, you are trying to tell the main queue to wait for itself - which is obviously nonsense and therefore it causes crash.
In the second case, you run the code on the background thread, and then you use main.sync to wait until the main thread can run the block provided in main.sync.
In general, I would use async and not sync all the time, unless sync is necessary - and always sync one thread (DispatchQueue) with a different one, never with the same one.
You were mixing up the terms sync/async and main/global.
Sync - Run some task synchronously (i.e. the thread which can be main/global/any other thread will wait for the task to complete)
Async - Run some task asynchronously (i.e. the thread which can be main/global/any other thread will push the task to a queue and continue executing next steps outside your block. It wont wait)
Now lets go one by one in your code which was crashing :
Lets put some names for our threads so it will be easy for our understanding:
1) ThreadA - Which encounters your dispatch statements (this can also be Main thread, but for explanation purpose I feel its better)
2) ThreadB - Global thread which gets created when you submit some task.
3) ThreadMain - Main thread
Example A:
DispatchQueue.main.async - ThreadA comes and execute this statement and put your block on ThreadMain and moves on (since its async) to next steps after the block. Now lets talk about ThreadMain, what it will do from here. Since ThreadMain got a block (submitted by ThreadA) it starts executing step by step and suddenly it sees 'DispatchQueue.main.sync' and submits the inner block on to the same TheradMain queue and keeps onnnnn waitingggggg (since its sync). So literally you are making the ThreadMain into deadlock situation.