dispatch sync of main queue related query [ios, swift] - ios

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

Related

GCD Serial Queue dispatch async and sync

I have some doubts regarding GCD.
Code snippet 1
serialQ.sync {
print(1)
serialQ.async {
print(2)
}
serialQ.async {
print(3)
}
}
Code snippet 2
serialQ.async {
print(1)
serialQ.async {
print(2)
}
serialQ.sync {
print(3)
}
}
I ran both of them in playground, and found that Code snippet 2 gives deadlock while Code snippet 1 runs fine. I have read a lot about GCD and started playing around with these concepts. Can anyone please provide a detailed explanation for the same ?
PS : serialQ is a serial Queue
According to my understanding,
Serial Queue - generates only one thread at a time, and once that thread is freed up then it is occupied or free to do other tasks
Serial Queue dispatched sync - blocks the caller thread from which the serial queue is dispatched and performs the tasks on that thread.
Serial Queue dispatched async - does'nt not blocks the caller thread, infact it runs on a different thread and keeps the caller
thread running.
But for the above query I am not able to get the proper explanation.
You are calling sync inside a block already executing on the same queue. This will always cause a deadlock. sync is the equivalent of saying “execute this now and wait for it to return.” Since you are already executing on that queue, the queue never becomes available to execute the sync block. It sounds like you’re looking for recursive locks, but that’s not how queues work. It’s also quite arguably an anti-pattern in general. I discuss this more in this answer: How to implement a reentrant locking mechanism in objective-c through GCD?
EDIT: Came back to add some thoughts on your "understandings":
Serial Queue - generates only one thread at a time, and once that thread is freed up then it is occupied or free to do other tasks
A serial queue doesn't "generate" one thread. Queues and threads are different things and have different semantics. A serial queue requires one thread upon which to execute a work item, but there's not a one-to-one relationship between a serial queue and a thread. A thread is a relatively "heavy" resource and a queue is a relatively "light" resource. A single serial queue can execute work items on more than one thread over its lifetime (although never more than one thread at the same time). GCD maintains pools of threads that it uses to execute work items, but that is an implementation detail, and it's not necessary to understand how that's implemented in order to use queues properly.
Serial Queue dispatched sync - blocks the caller thread from which the serial queue is dispatched and performs the tasks on that thread.
A queue (serial or concurrent) is not "dispatched" (sync or otherwise). A work item is, well, enqueued into a queue. That work item will be subsequently executed by an arbitrary thread including, quite possibly, the calling thread. The guarantee is that only one work item enqueued to a given serial queue will be executing (on any thread) at one time.
Serial Queue dispatched async - doesn't block the ~caller~ enqueueing thread, in fact it runs on a different thread and keeps the caller thread running. (minor edits for readability)
This is close, but not quite accurate. It's true that enqueueing a work item onto a serial queue with async does not block the enqueueing thread. It's not necessarily true that the work item is executed by a different thread than the enqueueing thread, although in the common case, that is usually the case.
The thing to know here is that the difference between sync and async is strictly limited to the behavior of the enqueueing thread and has no (guaranteed) bearing or impact on which thread the work item is executed. If you enqueue a work item with sync the enqueueing thread will wait (possibly forever, in the specific case you outlined here) for the work item to complete, whereas if you enqueue a work item with async the enqueueing thread will continue executing.
A sync call, as you point out, blocks the current thread until the block runs. So when you make sync to the same serial queue that you’re currently on, you are blocking the queue, waiting for a block to run on the same queue that you just blocked, resulting in a deadlock.
If you really want to run something synchronously on the current queue, don’t dispatch it with sync at all and just run it directly. E.g.:
serialQ.async {
print(1)
serialQ.async {
print(2)
}
// serialQ.sync { // don't dispatch synchronously to the current serial queue
print(3)
// }
}
Or dispatch asynchronously. E.g.,
serialQ.async {
print(1)
serialQ.async {
print(2)
}
serialQ.async {
print(3)
}
}
Or use a concurrent queue (in which case you have to be careful to make sure you don’t have thread explosion, which could result in deadlock, too). E.g.,
let concurrentQ = DispatchQueue(label: "...", attributes: .concurrent)
concurrentQ.async {
print(1)
concurrentQ.async {
print(2)
}
concurrentQ.sync {
print(3)
}
}

Why we use async on main thread

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.

DispatchQueue crashing with main.sync in Swift

Please explain to me why I am getting this crash?
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
in this
DispatchQueue.main.sync {
print("sync")
}
This is my code.
override func viewDidLoad() {
super.viewDidLoad()
print("Start")
DispatchQueue.main.async {
print("async")
}
DispatchQueue.main.sync {
print("sync")
}
print("Finish")
}
NEVER call the sync function on the main queue
If you call the sync function on the main queue it will block the queue as well as the queue will be waiting for the task to be completed but the task will never be finished since it will not be even able to start due to the queue is already blocked. It is called deadlock.
Two (or sometimes more) items — in most cases, threads — are said to be deadlocked if they all get stuck waiting for each other to complete or perform another action. The first can’t finish because it’s waiting for the second to finish. But the second can’t finish because it’s waiting for the first to finish.
You need to be careful though. Imagine if you call sync and target the current queue you’re already running on. This will result in a deadlock situation.
Use sync to keep track of your work with dispatch barriers, or when you need to wait for the operation to finish before you can use the data processed by the closure.
When to use sync?
When we need to wait until the task is finished. F.e. when we are making sure that some function/method is not double called. F.e. we have synchronization and trying to prevent it to be double called until it's completely finished.
When you need to wait for something done on a DIFFERENT queue and only then continue working on your current queue
Synchronous vs. Asynchronous
With GCD, you can dispatch a task either synchronously or asynchronously.
A synchronous function returns control to the caller after the task is completed.
An asynchronous function returns immediately, ordering the task to be done but not waiting for it. Thus, an asynchronous function does not block the current thread of execution from proceeding on to the next function.
#sankalap, Dispatch.main is a serial queue which has single thread to execute all the operations. If we call "sync" on this queue it will block all other operations currently running on the thread and try to execute the code block inside sync whatever you have written. This results in "deadlock".
As per Apple documentation on executing dispatch_sync on a queue you're currently on will crash your code:
Calling this function and targeting the current queue results in
deadlock.
Because the current queue is the main queue, when you continue to call sync on the main queue, the system will understand that current main queue must wait some code complete in current queue, but no code at current queue (main queue), so you wait forever:
Apple document: Calling this function and targeting the current queue results in deadlock.

GCD Main Thread Crash Issue (Explanation Needed)?

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.

Main thread run asynchronously or synchronously in iOS?

Does the Main thread run asynchronously or synchronously in iOS?
Please explain with examples.
Your question doesn't really make sense.
The thread doesn't run synchronously or asynchronously. Tasks are dispatched onto threads.
The main queue is a serial dispatch queue, so it only executes a single task at a time, and that task is always executed on the main thread. Tasks can be added to the main queue (and indeed any queue) synchronously or asynchronously and this is the problem with your question; synchronous or asynchronous dispatch is relative to the task that is dispatching the new task.
Main thread runs synchronously. All UI related operations should be preformed on main thread.
Server calls like downloading or uploading should be performed asynchronously ( on background thread ).
I think the question is not how the main thread runs, but how YOU run on it. For example - the main dispatch queue (runs on the main thread) can be referenced as sync or async:
DispatchQueue.main.async {
print("async on the main thread")
}
DispatchQueue.main.sync {
print("sync on the main thread")
}

Resources