Why does DispatchGroup interfere with main queue? - ios

I have to stop my code for one second in order to get server databases synced before continuing.
All code snippets below are run from main thread.
I used this first:
// code 1
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
// dummy
}
// stuff
Outcome obviously not as desired since stuff is executed immediately. I'd like stuff to run delayed after the code block (not nice, but there's a reason for it).
// code 2
let group = DispatchGroup()
group.enter()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
group.leave()
}
group.wait()
// stuff
Deadlock!
Questions 1 : Why is main queue without DispatchGroup working and locks in conjunction with DispatchGroup?
// code 3
let group = DispatchGroup()
group.enter()
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
group.leave()
}
group.wait()
// stuff
This works as desired!
Question 2 : Why is global queue required with DispatchGroup to make stuff run delayed?
I read this here:
https://stackoverflow.com/a/42773392/3657691/ https://stackoverflow.com/a/28821805/3657691/ https://stackoverflow.com/a/49287115/3657691/

I am going to assume that you are running these snippets on the main thread, as this is most probably the case from the issue description.
Dispatch queues are basically task queues, so a queue has some tasks enqueued. Let's see what is on the main queue when you are executing the snippet generating a deadlock.
The main queue has a task executing (the one executing the snippet)
Then you call asyncAfter which will enqueue another task (the closure containing group.leave()) on the main queue, after the specified deadline.
Now the task being executed (1.) is being blocked by the call to group.wait(), and it's going to block the whole main queue until it finishes execution. This means the enqueued task (2.) will have to wait until the first one finishes. You can see here that the 2 tasks will block each other:
the first one (1.) will wait until the second one (2.) releases the dispatch group
the second one (2.) will wait until the first one (1.) finishes execution so it can be scheduled
For question number 2, using a global queue (or literally any other queue other than the one our current code is being executed on - in this example the main queue), will not block the asyncAfter task (obviously, because it's being scheduled on another queue which is not blocked, thus it gets the chance to be executed).
This is true for serial dispatch queues (the main queue being a serial queue as well). Serial dispatch queues will execute their tasks serially, that is only one at a time.
On the other hand, for a concurrent dispatch queue, this scenario won't result in a deadlock, because the asyncAfter task won't be blocked by the waiting task. That's because concurrent dispatch queues don't wait for the task being executed to be finished to schedule the next enqueued task.
This would even be a good exercise to try to run this scenario on a serial queue, and then on a concurrent queue to observe the differences

Related

DispatchQueue.sync { } blocks "thread" or "queue"

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).

What happens if dispatch on same queue?

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.

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.

main.async vs main.sync() vs global().async in Swift3 GCD

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.

Concurrent vs serial queues in GCD

I'm struggling to fully understand the concurrent and serial queues in GCD. I have some issues and hoping someone can answer me clearly and at the point.
I'm reading that serial queues are created and used in order to execute tasks one after the other. However, what happens if:
I create a serial queue
I use dispatch_async (on the serial queue I just created) three times to dispatch three blocks A,B,C
Will the three blocks be executed:
in order A,B,C because the queue is serial
OR
concurrently (in the same time on parralel threads) because I used ASYNC dispatch
I'm reading that I can use dispatch_sync on concurrent queues in order to execute blocks one after the other. In that case, WHY do serial queues even exist, since I can always use a concurrent queue where I can dispatch SYNCHRONOUSLY as many blocks as I want?
Thanks for any good explanation!
A simple example: you have a block that takes a minute to execute. You add it to a queue from the main thread. Let's look at the four cases.
async - concurrent: the code runs on a background thread. Control returns immediately to the main thread (and UI). The block can't assume that it's the only block running on that queue
async - serial: the code runs on a background thread. Control returns immediately to the main thread. The block can assume that it's the only block running on that queue
sync - concurrent: the code runs on a background thread but the main thread waits for it to finish, blocking any updates to the UI. The block can't assume that it's the only block running on that queue (I could have added another block using async a few seconds previously)
sync - serial: the code runs on a background thread but the main thread waits for it to finish, blocking any updates to the UI. The block can assume that it's the only block running on that queue
Obviously you wouldn't use either of the last two for long running processes. You normally see it when you're trying to update the UI (always on the main thread) from something that may be running on another thread.
Here are a couple of experiments that i have done to make me understand about these serial, concurrent queues with Grand Central Dispatch.
func doLongAsyncTaskInSerialQueue() {
let serialQueue = DispatchQueue(label: "com.queue.Serial")
for i in 1...5 {
serialQueue.async {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("\(i) completed downloading")
}
}
}
Task will run in different thread(other than main thread) when you use async in GCD. Async means execute next line do not wait until the block executes which results non blocking main thread & main queue.
Since its serial queue, all are executed in the order they are added to serial queue.Tasks executed serially are always executed one at a time by the single thread associated with the Queue.
func doLongSyncTaskInSerialQueue() {
let serialQueue = DispatchQueue(label: "com.queue.Serial")
for i in 1...5 {
serialQueue.sync {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("\(i) completed downloading")
}
}
}
Task may run in main thread when you use sync in GCD. Sync runs a block on a given queue and waits for it to complete which results in blocking main thread or main queue.Since the main queue needs to wait until the dispatched block completes, main thread will be available to process blocks from queues other than the main queue.Therefore there is a chance of the code executing on the background queue may actually be executing on the main thread
Since its serial queue, all are executed in the order they are added(FIFO).
func doLongASyncTaskInConcurrentQueue() {
let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
for i in 1...5 {
concurrentQueue.async {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("\(i) completed downloading")
}
print("\(i) executing")
}
}
Task will run in background thread when you use async in GCD. Async means execute next line do not wait until the block executes which results non blocking main thread.
Remember in concurrent queue, task are processed in the order they are added to queue but with different threads attached to the
queue. Remember they are not supposed to finish the task as the order
they are added to the queue.Order of task differs each time
threads are created as necessarily automatically.Task are executed in parallel. With more than
that(maxConcurrentOperationCount) is reached, some tasks will behave
as a serial until a thread is free.
func doLongSyncTaskInConcurrentQueue() {
let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
for i in 1...5 {
concurrentQueue.sync {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("\(i) completed downloading")
}
print("\(i) executed")
}
}
Task may run in main thread when you use sync in GCD. Sync runs a block on a given queue and waits for it to complete which results in blocking main thread or main queue.Since the main queue needs to wait until the dispatched block completes, main thread will be available to process blocks from queues other than the main queue.Therefore there is a chance of the code executing on the background queue may actually be executing on the main thread.
Since its concurrent queue, tasks may not finish in the order they are added to queue. But with synchronous operation it does although they may be processed by different threads. So, it behaves as this is the serial queue.
Here is a summary of these experiments
Remember using GCD you are only adding task to the Queue and performing task from that queue. Queue dispatches your task either in main or background thread depending on whether operation is synchronous or asynchronous. Types of queues are Serial,Concurrent,Main dispatch queue.All the task you perform is done by default from Main dispatch queue.There are already four predefined global concurrent queues for your application to use and one main queue(DispatchQueue.main).You can also manually create your own queue and perform task from that queue.
UI Related task should always be performed from main thread by dispatching the task to Main queue.Short hand utility is DispatchQueue.main.sync/async whereas network related/heavy operations should always be done asynchronously no matters which ever thread you are using either main or background
EDIT:
However, There are cases you need to perform network calls operations synchronously in a background thread without freezing UI(e.g.refreshing OAuth Token and wait if it succeed or not).You need to wrap that method inside a asynchronous operation.This way your heavy operations are executed in the order and without Blocking main thread.
func doMultipleSyncTaskWithinAsynchronousOperation() {
let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
concurrentQueue.async {
let concurrentQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
for i in 1...5 {
concurrentQueue.sync {
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("\(i) completed downloading")
}
print("\(i) executed")
}
}
}
EDIT EDIT: You can watch demo video here
First, it's important to know the difference between threads and queues and what GCD really does. When we use dispatch queues (through GCD), we're really queueing, not threading. The Dispatch framework was designed specifically to get us away from threading, as Apple admits that "implementing a correct threading solution [can] become extremely difficult, if not [sometimes] impossible to achieve." Therefore, to perform tasks concurrently (tasks that we don't want freezing the UI), all we need to do is create a queue of those tasks and hand it to GCD. And GCD handles all of the associated threading. Therefore, all we're really doing is queueing.
The second thing to know right away is what a task is. A task is all of the code within that queue block (not within the queue, because we can add things to a queue all of the time, but within the closure where we added it to the queue). A task is sometimes referred to as a block and a block is sometimes referred to as a task (but they are more commonly known as tasks, particularly in the Swift community). And no matter how much or little code, all of the code within the curly braces are considered a single task:
serialQueue.async {
// this is one task
// it can be any number of lines with any number of methods
}
serialQueue.async {
// this is another task added to the same queue
// this queue now has two tasks
}
And it's obvious mentioning that concurrent simply means at the same time with other things and serial means one after the other (never at the same time). To serialize something, or to put something in serial, just means to execute it from start to finish in its order from left to right, top to bottom, uninterrupted.
There are two types of queues, serial and concurrent, but all queues are concurrent relative to each other. The fact that you want to run any code "in the background" means that you want to run it concurrently with another thread (usually the main thread). Therefore, all dispatch queues, serial or concurrent, execute their tasks concurrently relative to other queues. Any serialization performed by queues (by serial queues), have only to do with the tasks within that single [serial] dispatch queue (like in the example above where there are two tasks within the same serial queue; those tasks will be executed one after the other, never simultaneously).
SERIAL QUEUES (often known as private dispatch queues) guarantee the execution of tasks one at a time from start to finish in the order that they were added to that specific queue. This is the only guarantee of serialization anywhere in the discussion of dispatch queues--that the specific tasks within a specific serial queue are executed in serial. Serial queues can, however, run simultaneously with other serial queues if they are separate queues because, again, all queues are concurrent relative to each other. All tasks run on distinct threads but not every task is guaranteed to run on the same thread (not important, but interesting to know). And the iOS framework does not come with any ready-to-use serial queues, you must make them. Private (non-global) queues are serial by default, so to create a serial queue:
let serialQueue = DispatchQueue(label: "serial")
You can make it concurrent through its attribute property:
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])
But at this point, if you aren't adding any other attributes to the private queue, Apple recommends that you just use one of their ready-to-go global queues (which are all concurrent). At the bottom of this answer, you'll see another way to create serial queues (using the target property), which is how Apple recommends doing it (for more efficient resource management). But for now, labeling it is sufficient.
CONCURRENT QUEUES (often known as global dispatch queues) can execute tasks simultaneously; the tasks are, however, guaranteed to initiate in the order that they were added to that specific queue, but unlike serial queues, the queue does not wait for the first task to finish before starting the second task. Tasks (as with serial queues) run on distinct threads and (as with serial queues) not every task is guaranteed to run on the same thread (not important, but interesting to know). And the iOS framework comes with four ready-to-use concurrent queues. You can create a concurrent queue using the above example or by using one of Apple's global queues (which is usually recommended):
let concurrentQueue = DispatchQueue.global(qos: .default)
RETAIN-CYCLE RESISTANT: Dispatch queues are reference-counted objects but you do not need to retain and release global queues because they
are global, and thus retain and release is ignored. You can access
global queues directly without having to assign them to a property.
There are two ways to dispatch queues: synchronously and asynchronously.
SYNC DISPATCHING means that the thread where the queue was dispatched (the calling thread) pauses after dispatching the queue and waits for the task in that queue block to finish executing before resuming. To dispatch synchronously:
DispatchQueue.global(qos: .default).sync {
// task goes in here
}
ASYNC DISPATCHING means that the calling thread continues to run after dispatching the queue and does not wait for the task in that queue block to finish executing. To dispatch asynchronously:
DispatchQueue.global(qos: .default).async {
// task goes in here
}
Now one might think that in order to execute a task in serial, a serial queue should be used, and that's not exactly right. In order to execute multiple tasks in serial, a serial queue should be used, but all tasks (isolated by themselves) are executed in serial. Consider this example:
whichQueueShouldIUse.syncOrAsync {
for i in 1...10 {
print(i)
}
for i in 1...10 {
print(i + 100)
}
for i in 1...10 {
print(i + 1000)
}
}
No matter how you configure (serial or concurrent) or dispatch (sync or async) this queue, this task will always be executed in serial. The third loop will never run before the second loop and the second loop will never run before the first loop. This is true in any queue using any dispatch. It's when you introduce multiple tasks and/or queues where serial and concurrency really come into play.
Consider these two queues, one serial and one concurrent:
let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)
Say we dispatch two concurrent queues in async:
concurrentQueue.async {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
101
2
102
103
3
104
4
105
5
Their output is jumbled (as expected) but notice that each queue executed its own task in serial. This is the most basic example of concurrency--two tasks running at the same time in the background in the same queue. Now let's make the first one serial:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
101
1
2
102
3
103
4
104
5
105
Isn't the first queue supposed to be executed in serial? It was (and so was the second). Whatever else happened in the background is not of any concern to the queue. We told the serial queue to execute in serial and it did... but we only gave it one task. Now let's give it two tasks:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
2
3
4
5
101
102
103
104
105
And this is the most basic (and only possible) example of serialization--two tasks running in serial (one after the other) in the background (to the main thread) in the same queue. But if we made them two separate serial queues (because in the above example they are the same queue), their output is jumbled again:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue2.async {
for i in 1...5 {
print(i + 100)
}
}
1
101
2
102
3
103
4
104
5
105
And this is what I meant when I said all queues are concurrent relative to each other. These are two serial queues executing their tasks at the same time (because they are separate queues). A queue does not know or care about other queues. Now lets go back to two serial queues (of the same queue) and add a third queue, a concurrent one:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue.async {
for i in 1...5 {
print(i + 100)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 1000)
}
}
1
2
3
4
5
101
102
103
104
105
1001
1002
1003
1004
1005
That's kind of unexpected, why did the concurrent queue wait for the serial queues to finish before it executed? That's not concurrency. Your playground may show a different output but mine showed this. And it showed this because my concurrent queue's priority wasn't high enough for GCD to execute its task sooner. So if I keep everything the same but change the global queue's QoS (its quality of service, which is simply the queue's priority level) let concurrentQueue = DispatchQueue.global(qos: .userInteractive), then the output is as expected:
1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105
The two serial queues executed their tasks in serial (as expected) and the concurrent queue executed its task quicker because it was given a high priority level (a high QoS, or quality of service).
Two concurrent queues, like in our first print example, show a jumbled printout (as expected). To get them to print neatly in serial, we would have to make both of them the same serial queue (the same instance of that queue, as well, not just the same label). Then each task is executed in serial with respect to the other. Another way, however, to get them to print in serial is to keep them both concurrent but change their dispatch method:
concurrentQueue.sync {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
2
3
4
5
101
102
103
104
105
Remember, sync dispatching only means that the calling thread waits until the task in the queue is completed before proceeding. The caveat here, obviously, is that the calling thread is frozen until the first task completes, which may or may not be how you want the UI to perform.
And it is for this reason that we cannot do the following:
DispatchQueue.main.sync { ... }
This is the only possible combination of queues and dispatching methods that we cannot perform—synchronous dispatching on the main queue. And that's because we are asking the main queue to freeze until we execute the task within the curly braces... which we dispatched to the main queue, which we just froze. This is called deadlock. To see it in action in a playground:
DispatchQueue.main.sync { // stop the main queue and wait for the following to finish
print("hello world") // this will never execute on the main queue because we just stopped it
}
// deadlock
One last thing to mention is resources. When we give a queue a task, GCD finds an available queue from its internally-managed pool. As far as the writing of this answer, there are 64 queues available per qos. That may seem like a lot but they can quickly be consumed, especially by third-party libraries, particularly database frameworks. For this reason, Apple has recommendations about queue management (mentioned in the links below); one being:
Instead of creating private concurrent queues, submit tasks to one of
the global concurrent dispatch queues. For serial tasks, set the
target of your serial queue to one of the global concurrent queues.
That way, you can maintain the serialized behavior of the queue while
minimizing the number of separate queues creating threads.
To do this, instead of creating them like we did before (which you still can), Apple recommends creating serial queues like this:
let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))
And using an extension, we can get it down to this:
extension DispatchQueue {
public class func serial(label: String, qos: DispatchQoS = .default) -> DispatchQueue {
return DispatchQueue(label: label,
qos: qos,
attributes: [],
autoreleaseFrequency: .inherit,
target: .global(qos: qos.qosClass))
}
}
let defaultSerialQueue = DispatchQueue.serial(label: "xyz")
let serialQueue = DispatchQueue.serial(label: "xyz", qos: .userInteractive)
// Which now looks like the global initializer
let concurrentQueue = DispatchQueue.global(qos: .default)
For further reading, I recommend the following:
https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1
https://developer.apple.com/documentation/dispatch/dispatchqueue
I like to think this using this metaphor (Here's the link to the original image):
Let's imagine your dad is doing the dishes and you've just had a glass of soda. You bring the glass to your dad to clean it up, putting it besides the other dish.
Now your dad is doing the dishes all by himself, so he's going to have to do them one by one: Your dad here represents a serial queue.
But you're not really interested in standing there and watching it get cleaned up. So, you drop the glass, and go back to your room: this is called an async dispatch. Your dad might or might not let you know once he's done but the important bit is that you're not waiting for the glass to be cleaned up; you go back to your room to do, you know, kid stuff.
Now let's assume you're still thirsty and want to have some water on that same glass that happens to be your favourite, and you really want it back as soon as it's cleaned up. So, you stand there and watch your dad doing the dishes until yours is done. This is a sync dispatch, since you're blocked while you are waiting for the task to be finished.
And finally let's say your mom decides to help your dad and joins him doing the dishes. Now the queue becomes a concurrent queue since they can clean multiple dishes at the same time; but note that you can still decide to wait there or go back to your room, regardless of how they work.
Hope this helps
If I understand correctly about how GCD works, I think there are two types of DispatchQueue, serial and concurrent, at the same time, there are two way how DispatchQueue dispatch its tasks, the assigned closure, first one is async, and the other is sync. Those together determines how the closure (task) actually is executed.
I found that serial and concurrent mean how many threads that queue can use, serial means one, whereas concurrent means many. And sync and async mean the task will be executed on which thread, the caller's thread or the thread underlying that queue, sync means run on caller's thread whereas async means run on the underlying thread.
The following is experimental code that can run on Xcode playground.
PlaygroundPage.current.needsIndefiniteExecution = true
let cq = DispatchQueue(label: "concurrent.queue", attributes: .concurrent)
let cq2 = DispatchQueue(label: "concurent.queue2", attributes: .concurrent)
let sq = DispatchQueue(label: "serial.queue")
func codeFragment() {
print("code Fragment begin")
print("Task Thread:\(Thread.current.description)")
let imgURL = URL(string: "http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground")!
let _ = try! Data(contentsOf: imgURL)
print("code Fragment completed")
}
func serialQueueSync() { sq.sync { codeFragment() } }
func serialQueueAsync() { sq.async { codeFragment() } }
func concurrentQueueSync() { cq2.sync { codeFragment() } }
func concurrentQueueAsync() { cq2.async { codeFragment() } }
func tasksExecution() {
(1...5).forEach { (_) in
/// Using an concurrent queue to simulate concurent task executions.
cq.async {
print("Caller Thread:\(Thread.current.description)")
/// Serial Queue Async, tasks run serially, because only one thread that can be used by serial queue, the underlying thread of serial queue.
//serialQueueAsync()
/// Serial Queue Sync, tasks run serially, because only one thread that can be used by serial queue,one by one of the callers' threads.
//serialQueueSync()
/// Concurrent Queue Async, tasks run concurrently, because tasks can run on different underlying threads
//concurrentQueueAsync()
/// Concurrent Queue Sync, tasks run concurrently, because tasks can run on different callers' thread
//concurrentQueueSync()
}
}
}
tasksExecution()
Hope it can be helpful.
1. I am reading that serial queues are created and used in order to execute tasks one after the other . However, what happens if:-
• I create a serial queue
• I use dispatch_async (on the serial queue I just created) three times to dispatch three blocks A,B,C
ANSWER:-
All three blocks executed one after the another.I have created one sample code that helps to understand.
let serialQueue = DispatchQueue(label: "SampleSerialQueue")
//Block first
serialQueue.async {
for i in 1...10{
print("Serial - First operation",i)
}
}
//Block second
serialQueue.async {
for i in 1...10{
print("Serial - Second operation",i)
}
}
//Block Third
serialQueue.async {
for i in 1...10{
print("Serial - Third operation",i)
}
}

Resources