Difference Between Dispatchqueue.Main.Async and Dispatchqueue.Main.Sync

Difference between DispatchQueue.main.async and DispatchQueue.main.sync

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.

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

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

Difference Between DispatchQueue.sync vs DispatchQueue.async

Sync will stop the current thread until it has finished the task you are assigning to it.

Async will continue with the current thread and will execute the task in parallel or after the current thread.

Why it has unexpected behaviour?

That is because loadView() expects to have a UIView assigned to the view property after it has been executed, which you are doing it with async, which will be executed after loadView finishes.

The exception might be because you are not assigning a UIView on time or because you are handling the UI in your private Queue. UI should always be handled in the main thread.

Your variable que is a private queue, and because you didn't specify otherwise it is pointing to a background thread.

Editing your code like this might help you:

import UIKit
import PlaygroundSupport

class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white

let que = DispatchQueue.init(label: "testing")
// Executed 3 times
que.sync {
for i in 0...10 {
print(i)
}
}
// Giving me NSException
DispatchQueue.main.async {
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black

view.addSubview(label)

print("Label Added to Text View")
}
self.view = view
}
}

// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

Why calling `DispatchQueue.main.sync` asynchronously from concurrent queue succeeds but synchronously fails?

.sync means it will block currently working thread, and wait until the closure has been executed. So your first .sync will block the main thread (you must be executing the .sync in the main thread otherwise it won't be deadlock). And wait until the closure in background.sync {...} has been finished, then it can continue.

But the second closure blocks the background thread and assign a new job to main thread, which has been blocked already. So these two threads are waiting for each other forever.

But if you switch your start context, like start your code in a background thread, could resolve the deadlock.


// define another background thread
let background2 = DispatchQueue(label: "backgroundQueue2",
qos: .background,
attributes: [],
autoreleaseFrequency: .inherit,
target: nil)
// don't start sample code in main thread.
background2.async {
background.sync {
DispatchQueue.main.sync {
print("Hello from background sync")
}
}
}

These deadlock is caused by .sync operation in a serial queue. Simply call DispatchQueue.main.sync {...} will reproduce the problem.

// only use this could also cause the deadlock.
DispatchQueue.main.sync {
print("Hello from background sync")
}

Or don't block the main thread at the very start could also resolve the deadlock.

background.async {
DispatchQueue.main.sync {
print("Hello from background sync")
}
}

Conclusion

.sync operation in a serial queue could cause permanent waiting because it's single threaded. It can't be stopped immediately and looking forward to a new job. The job it's doing currently should be done by first, then it can start another. That's why .sync could not be used in a serial queue.

What does main.sync in global().async mean?

x.sync means that the calling queue will pause and wait until the sync block finishes to continue. so in your example:

DispatchQueue.global().async {
// yada yada something
DispatchQueue.main.sync {
// update UI
}
// this will happen only after 'update UI' has finished executing
}

Usually you don't need to sync back to main, async is probably good enough and safer to avoid deadlocks. Unless it is a special case where you need to wait until something finishes on main before continuing with your async task.

As for A example crashing - 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.

As for scheduling multiple blocks on main queue with async: they won't be run in parallel - they will happen one after another.
Also don't assume that queue == thread. Scheduling multiple blocks onto the same queue, might create as many threads as system allow. Just the main queue is special that it utilises Main thread.

Why does dispatchQueue.main give me the option of sync or async if main queue is always sync

The main queue is no different than any other queue with regard to whether you can enqueue something to it synchronously or asynchronously.

The only rule you must adhere to is to never use sync to enqueue something onto the same queue currently being used. That will cause a deadlock. Again, this is true no matter the queue, main or otherwise.

To answer your question "Is the same as" - no, it is not the same. Assuming both sets of code are being called from some background queue, in the first set of code, the background queue will keep on moving along without regard to when the two blocks eventually get executed on the main queue. In the second set of code (using sync), the background queue blocks each time until each block of code is run on the main queue.

If both sets of code are being called from the main queue, then there is a bigger difference. The first set of code (with async) keeps working. The second set of code (with sync) will cause the main queue to block at the first call to sync and your app will become unresponsive until the user (or the OS) kills it.

The only possibly relevant difference between the main queue and other queues is that the main queue is always a serial queue while background queues can be either serial or concurrent. But both have valid uses for using sync or async as long as you avoid using sync where both queues are the same.

Swift Threading: When to use DispatchQueue.main.async?

The primary use of DispatchQueue.main.async is when you have code running on a background queue and you need a specific block of code to be executed on the main queue.

In your code, viewDidLoad is already running on the main queue so there is little reason to use DispatchQueue.main.async.

But isn't necessarily wrong to use it. But it does change the order of execution.

Example without:

class MyViewController: UIViewController {
func updateUI() {
print("update")
}

override func viewDidLoad() {
super.viewDidLoad()
print("before")
updateUI()
print("after")
}
}

As one might expect, the output will be:

before

update

after

Now add DispatchQueue.main.async:

class MyViewController: UIViewController {
func updateUI() {
print("update")
}

override func viewDidLoad() {
super.viewDidLoad()
print("before")
DispatchQueue.main.async {
updateUI()
}
print("after")
}
}

And the output changes:

before

after

update

This is because the async closure is queued up to run after the current runloop completes.

DispatchQueue.sync { } blocks thread or 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:

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



Related Topics



Leave a reply



Submit