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
Related
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
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.
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.
I want to know As we all know how asynchronous task are necessary for concurrency but Wanted to know why we need the synchronous tasks. while we can achieve the same with the normal usage of function.
Thanks & regards
Rohit
When you calls something synchronously, it means that 'the thread that initiated that operation will wait for the task to finish before
continuing'. Asynchronous means that it will not wait for finish the task.
synchronous calls stops your current action and returns when the call returned. with asynchronous calls you can continue.
synchronous is the opposite of asynchronous code, and therefore is ordinary code.
At the end, if asynchronous is totally out of scope then you will not emphasize the word synchronous.
It helps to synchronise threads, as the name suggests.
consider a typical usage of GCD async and sync (pseudo)
async background_thread {
//1 call webservice or other long task that would block the main thread
sync main_thread {
//2 update UI with results from 1
}
//3 do something else that relies on 2
}
now if 2 was in an async and you needed to do something at 3 that relies on the updates at 2 to have happened, then you are not guaranteed (and most likely wont) get the behaviour you are expecting. instead, you use a sync to make sure that the task is completed before continuing the execution in the background thread.
If you are asking now, why not just take out the sync/async around 2 so it executes in order anyway? the problem is, the UI must not be updated on a background thread otherwise the behaviour is undefined (which usually means the UI lags a lot). So in essence what happens is the background thread waits at 2's sync until the main thread gets round to executing that block, then it will continue with the rest of the execution on the background thread.
If you were dealing with a task that doesnt require the main thread (or some other thread) to execute properly, then yes you may as well take out the sync at 2.
This is just one example of how a sync is useful, there are others if you are doing advanced threading in your app.
Hope this helps
Typically it's because you want to do an operation on a specific different thread but you need the result of that operation. You cannot do the operation asynchronously because your code will proceed before the operation on the other thread completes.
Apple has a very nice example:
func asset() -> AVAsset? {
var theAsset : AVAsset!
self.assetQueue.sync {
theAsset = self.getAssetInternal().copy() as! AVAsset
}
return theAsset
}
Any thread might call the asset method; but to avoid problems with shared data, we require that only functions that are executed from a particular queue (self.assetQueue) may touch an AVAsset, so when we call getAssetInternal we do it on self.assetQueue. But we also need the result returned by our call to getAssetInternal; hence the call to sync rather than async.
I've been using NSOperationQueue's addOperationWithBlock: to run code in background threads, like so:
self.fetchDataQueue = NSOperationQueue()
for panel in self.panels {
self.fetchDataQueue.addOperationWithBlock() {
() -> Void in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
//Background code
}
}
}
I'm concerned that I may be doing this wrong. I can't see a way that the fetch queue would be able to know when an operation is done, since there's no completion to call, and I'm not confident it's tracking activity across threads to make sure it's still going.
And the point of using this is so that I don't queue them up in single file and take much longer to process, and so I don't run them all at once and use too much memory.
EDIT: I'm aware that I don't need to be doing dispatch_async, but it's simply an example of some block-based code I may call which may do the same thing, or a web request which may get back after a delay.
Well, your code will run in a background block. If you are using a queue to make sure that one operation only starts when the next one is finished, you may be in trouble: The block that you happen to the NSOperationQueue has finished as soon as it has dispatched the background code to GCD, not when the background code has actually finished which may be much later.