164

I have been using DispatchQueue.main.async for a long time to perform UI related operations.



Swift provides both DispatchQueue.main.async and DispatchQueue.main.sync, and both are performed on the main queue.



Can anyone tell me the difference between them? 

When should I use each?



DispatchQueue.main.async {
    self.imageView.image = imageView
    self.lbltitle.text = ""

}

DispatchQueue.main.sync {
    self.imageView.image = imageView
    self.lbltitle.text = ""
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
Aman.Samghani
  • 2,151
  • 3
  • 12
  • 27

4 Answers4

230

Why Concurrency?

As soon as you add heavy tasks to your app like data loading it slows your UI work down or even freezes it. Concurrency lets you perform 2 or more tasks “simultaneously”. The disadvantage of this approach is that thread safety which is not always as easy to control. F.e. when different tasks want to access the same resources like trying to change the same variable on a different threads or accessing the resources already blocked by the different threads.

There are a few abstractions we need to be aware of.

  • Queues.
  • Synchronous/Asynchronous task performance.
  • Priorities.
  • Common troubles.

Queues

Must be serial or concurrent. As well as global or private at the same time.

With serial queues, tasks will be finished one by one while with concurrent queues, tasks will be performed simultaneously and will be finished on unexpected schedules. The same group of tasks will take the way more time on a serial queue compared to a concurrent queue.

You can create your own private queues (both serial or concurrent) or use already available global (system) queues. The main queue is the only serial queue out of all of the global queues.

It is highly recommended to not perform heavy tasks which are not referred to UI work on the main queue (f.e. loading data from the network), but instead to do them on the other queues to keep the UI unfrozen and responsive to the user actions. If we let the UI be changed on the other queues, the changes can be made on a different and unexpected schedule and speed. Some UI elements can be drawn before or after they are needed. It can crash the UI. We also need to keep in mind that since the global queues are system queues there are some other tasks can run by the system on them.

Quality of Service / Priority

Queues also have different qos (Quality of Service) which sets the task performing priority (from highest to lowest here):
.userInteractive - as for the main queue
.userInitiated - for the user initiated tasks on which user waits for some response
.utility - for the tasks which takes some time and doesn't require immediate response, e.g working with data
.background - for the tasks which aren't related with the visual part and which aren't strict for the completion time).

There is also

.default queue which does't transfer the qos information. If it wasn't possible to detect the qos the qos will be used between .userInitiated and .utility.

Tasks can be performed synchronously or asynchronously.

  • Synchronous function returns control to the current queue only after the task is finished. It blocks the queue and waits until the task is finished.

  • Asynchronous function returns control to the current queue right after task has been sent to be performed on the different queue. It doesn't wait until the task is finished. It doesn't block the queue.

Common Troubles.

The most popular mistakes programmers make while projecting the concurrent apps are the following:

  • Race condition - caused when the app work depends on the order of the code parts execution.
  • Priority inversion - when the higher priority tasks wait for the smaller priority tasks to be finished due to some resources being blocked
  • Deadlock - when a few queues have infinite wait for the sources (variables, data etc.) already blocked by some of these queues.

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.

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. Here's some code for this concern:
How to find out what caused error crash report on IOS device?

Alexander
  • 2,803
  • 5
  • 13
  • 21
  • Hi Alexander, please confirm whether .userInteractive QoS is dispatched on main thread? Because I tried putting a breakpoint inside the async block and looked at the Debug Navigator active thread panel. When DispatchQueue.global(qos: .userInteractive).async{} is called from the main thread, it displays with a different name than the main thread – Nishu_Priya Aug 28 '17 at 07:07
  • Hi, Nishu, yes, .userInteractive is QoS used for the main thread. Which name does it display? – Alexander Aug 28 '17 at 20:33
  • 3
    I don't think that "NEVER call the sync function on the main queue" is right. There are cases when you would call the sync in main thread for example when you have a global counter that you need each object to use and increase the: dispatchQueue.sync { count += 1; self.orderId = count } – Elisha Sterngold Nov 28 '17 at 15:58
  • 9
    QOS Class - .userInteractive is NOT the main queue. – Kunal Shah Apr 09 '18 at 00:40
  • 1
    Would it be wrong to call `DispatchQueue.main.sync` from a background thread? – mfaani Oct 25 '18 at 23:51
  • 1
    @Honey, no that's not wrong to call that but from my experience, you would find yourself call more of DispatchQueue.main.async other than sync. – James Kim Mar 27 '19 at 23:02
  • As per my understanding, `sync` or `async` methods have no effect on the queue on which they are called. `sync` will block the thread __from__ which it is called and not the queue __on__ which it is called. It is the property of `DispatchQueue` which decides whether the `DispatchQueue` will wait for task execution (serial queue) or can run the next task before current task gets finished (concurrent queue). So even when `DispatchQueue.main.async` is an async call, it can block the main thread if some heavy duty work is done on it as `main` queue is a serial queue. – Vipin Dec 18 '19 at 13:34
  • 4
    Wouldn't it be more accurate to say that you should never call the sync() function on the current queue? It's not wrong to call sync() on the main queue if you're in another queue, if I understand correctly. – ykay Dec 20 '19 at 23:08
87

When you use async it lets the calling queue move on without waiting until the dispatched block is executed. On the contrary sync will make the calling queue stop and wait until the work you've dispatched in the block is done. Therefore sync is subject to lead to deadlocks. Try running DispatchQueue.main.sync from the main queue and the app will freeze because the calling queue will wait until the dispatched block is over but it won't be even able to start (because the queue is stopped and waiting)

When to use sync? When you need to wait for something done on a DIFFERENT queue and only then continue working on your current queue

Example of using sync:

On a serial queue you could use sync as a mutex in order to make sure that only one thread is able to perform the protected piece of code at the same time.

Andrey Chernukha
  • 21,488
  • 17
  • 97
  • 161
  • Would it be wrong to call `DispatchQueue.main.sync` from a background thread? – mfaani Oct 25 '18 at 23:52
  • 1
    @Honey In general no, there's nothing wrong with such a call (as long as main queue doesn't do anything heavy and time consuming), but in practice I can't think of a situation where you really need this. There should definitely be a better solution – Andrey Chernukha Oct 28 '18 at 07:48
  • 1
    @Honey One such situation is updating a CollectionView of PHAssets from the PhotoKit API, as demonstrated in the documentation here: https://developer.apple.com/documentation/photokit/phphotolibrarychangeobserver – teacup Oct 31 '18 at 01:56
  • 1
    @teacup interesting. I'm just wondering how it would be any different if we called `async` there? I mean since there is nothing else on the thread afterwards then it doesn't make a difference. If it was `DispatchQueue.main.sync {block1}; DispatchQueue.main.sync {block2};` then it would have made sense. But when there is no other block then I can't think of the benefit of using `DispatchQueue.main.sync {Oneblock}` over `DispatchQueue.main.async {Oneblock}`. For both of them they will get the mainQueue priority/immediacy and nothing would interrupt them. – mfaani Oct 31 '18 at 02:14
  • 4
    @Honey "since there is nothing else on the thread afterwards" isn't true when you're on the main thread, which is responsible for dealing with all user interactions with the app. So, for instance, a user might delete another photo before photoLibraryDidChange returns with an updated datasource causing a fatal inconsistency error. – teacup Oct 31 '18 at 02:56
  • @teacup won't it be dealing with all of them on the main thread? And isn't main thread a serial thread? Hence one at a time. The only time this can run into an issue is exactly because of _sequence_ or as you put it *before* something returns. My point is you won't run into concurrency issues. You would run into racing issues. Is that right? – mfaani Oct 31 '18 at 03:53
  • 1
    The main thread is and will always be a serial queue. Calling `main.async` or `main.sync` only changes the behavior of the queue you are calling *from*, affecting whether or not *that* queue is waiting for the block of code to be completed on the main queue. – Sean Jul 18 '20 at 19:56
8

DispatchQueue.<>.sync vs DispatchQueue.<>.async

[Sync vs Async]
[GCD]

GCD allows you to execute a task synchronously or asynchronously

synchronous(block and wait) function returns a control when the task will be completed

asynchronous(dispatch and proceed) function returns a control immediately, dispatching the task to start to an appropriate queue but not waiting for it to complete.

yoAlex5
  • 29,217
  • 8
  • 193
  • 205
2

sync or async methods have no effect on the queue on which they are called.

sync will block the thread from which it is called and not the queue on which it is called. It is the property of DispatchQueue which decides whether the DispatchQueue will wait for the task execution (serial queue) or can run the next task before current task gets finished (concurrent queue).

So even when DispatchQueue.main.async is an async call, a heavy duty operation added in it can freeze the UI as its operations are serially executed on the main thread. If this method is called from the background thread, control will return to that thread instantaneously even when UI seems to be frozen. This is because async call is made on DispatchQueue.main

Vipin
  • 1,725
  • 1
  • 14
  • 13
  • I guess you are wrong, the `sync` or `async` refers to the `Queue` As its a method on the `Queue` type, not `Thread` When you say myDispatchQueue.sync {} the queue is blocked, and the control will be stoped until the submitted work to be finished, not the thread, because you don't know which thread you will get when you submit a work to the queue that's why if you called `DispatchQueue.main.sync {}` form the main queue, your code will be frozen because the main QUEUE won't move until the Main Queue finishes, so I'm waiting until myself work, but I can't work cos I'm waiting, DeadLock! – Atef Dec 31 '21 at 05:46