Main.Async VS Main.Sync() VS Global().Async in Swift3 Gcd

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

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.

Swift DispatchQueue global and main in variable

The key issue is to ensure thread-safety. For example, the following is not thread-safe:

func addUpValuesNotThreadSafe() {
var total = 0

getMyFirstItem { value in
total += value // on main thread
}

getMySecondItem { value in
total += value // on some GCD worker thread!!!
}

getMyThirdItem { value in
total += value // on main thread
}

...
}

One could solve this problem by not allowing these tasks run in parallel, but you lose all the benefits of asynchronous processes and the concurrency they offer.

Needless to say, when you do allow them to run in parallel, you would likely add some mechanism (such as dispatch groups) to know when all of these asynchronous tasks are done. But I did not want to complicate this example, but rather keep our focus on the thread-safety issue. (I show how to use dispatch groups later in this answer.)

Anyway, if you have closures called from multiple threads, you must not increment the same total without adding some synchronization. You could add synchronization with a serial dispatch queue, for example:

func addUpValues() {
var total = 0
let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".synchronized")

getMyFirstItem { value in
queue.async {
total += value // on serial queue
}
}

getMySecondItem { value in
queue.async {
total += value // on serial queue
}
}

getMyThirdItem { value in
queue.async {
total += value // on serial queue
}
}

...
}

There are a variety of alternative synchronization mechanisms (locks, GCD reader-writer, actor, etc.). But I start with the serial queue example to observe that, actually, any serial queue would accomplish the same thing. Many use the main queue (which is a serial queue) for this sort of trivial synchronization where the performance impact is negligible, such as in this example.

For example, one could therefore either refactor getMySecondItem to also call its completion handler on the main queue, like getMyFirstItem and getMyThirdItem already do. Or if you cannot do that, you could simply have the getMySecondItem caller dispatch the code that needs to be synchronized to the main queue:

func addUpValues() {
var total = 0

getMyFirstItem { value in
total += value // on main thread
}

getMySecondItem { value in
DispatchQueue.main.async {
total += value // now on main thread, too
}
}

getMyThirdItem { value in
total += value // on main thread
}

// ...
}

That is also thread-safe. This is why many libraries will ensure that all of their completion handlers are called on the main thread, as it minimizes the amount of time the app developer needs to manually synchronize values.


While I have illustrated the use of serial dispatch queues for synchronization, there are a multitude of alternatives. E.g., one might use locks or GCD reader-writer pattern.

The key is that one should never mutate a variable from multiple threads without some synchronization.


Above I mention that you need to know when the three asynchronous tasks are done. You can use a DispatchGroup, e.g.:

func addUpValues(complete: @escaping (Int) -> Void) {
let total = Synchronized(0)
let group = DispatchGroup()

group.enter()
getMyFirstItem { first in
total.synchronized { value in
value += first
}
group.leave()
}

group.enter()
getMySecondItem { second in
total.synchronized { value in
value += second
}
group.leave()
}

group.enter()
getMyThirdItem { third in
total.synchronized { value in
value += third
}
group.leave()
}

group.notify(queue: .main) {
let value = total.synchronized { $0 }
complete(value)
}
}

And in this example, I abstracted the synchronization details out of addUpValues:

class Synchronized<T> {
private var value: T
private let lock = NSLock()

init(_ value: T) {
self.value = value
}

func synchronized<U>(block: (inout T) throws -> U) rethrows -> U {
lock.lock()
defer { lock.unlock() }
return try block(&value)
}
}

Obviously, use whatever synchronization mechanism you want (e.g., GCD or os_unfair_lock or whatever).

But the idea is that in the GCD world, dispatch groups can notify you when a series of asynchronous tasks are done.


I know that this was a GCD question, but for the sake of completeness, the Swift concurrency async-await pattern renders much of this moot.

func getMyFirstItem() async -> Int {
return 10
}

func getMySecondItem() async -> Int {
await Task.detached(priority: .background) {
return 10
}.value
}

func getMyThirdItem() async -> Int {
return 10
}

func addUpValues() {
Task {
async let value1 = getMyFirstItem()
async let value2 = getMySecondItem()
async let value3 = getMyThirdItem()
let total = await value1 + value2 + value3
print(total)
}
}

Or, if your async methods were updating some shared property, you would use an actor to synchronize access. See Protect mutable state with Swift actors.

If we call main.async while on a background queue when is the code executed?

There is no difference between enqueuing code on main using sync and async in term of when the code will be executed - in both cases the code will be executed as soon as it will become the first one in the main thread's queue. There is a queue of tasks that are supposed to happen on the main thread. You added something on that queue. When everything that was enqueued before your code will get executed, your code will get its turn, regardless if you added it using sync or async.

The only difference is in what happens with the calling thread - with sync the background thread becomes blocked until the code on the main thread will get executed; with async the background thread will continue.

Just a sidenote here - never call DispatchQueue.main.sync on main thread - it will cause a deadlock.

Grand Central Dispatch async vs sync

sync means the function WILL BLOCK the current thread until it has completed, async means it will be handled in the background and the function WILL NOT BLOCK the current thread.

If you want serial execution of blocks check out the creation of a serial dispatch queue

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.

GCD differences in Swift 3

Beside not weakifying self in the first approach, both calls are equivalent.

Whether or not to create a variable is up to your (convenience) preferences and does not make a difference technically.

Cost of calling DispatchQueue.main.async in iOS GCD

The important bit if the async dispatch.

If stpDispatchToMainThreadIfNecessary is called from the main thread, block is executed synchronously, while if the func is called from a background thread, due to the async(execute:) call, block is executed asynchronously.

This means that when called from the main thread, due to the sync execution, block will be executed sooner than if it was also wrapped in a Dispatch async call.

You could change the async(execute:) to sync(execute:), which in case of the main thread would result in no delay compared to simply calling block(), however, you should not be dispatching synchronously to the main thread from a background thread, because that might freeze the UI.

So checking whether the func was called from the main thread and in this case synchronously executing block, while asynchronously dispatching it to the main thread from any other thread ensures that there is no unnecessary blocking of the main thread when calling the func from another thread, but also no unnecessary delay when calling the func from the main thread.



Related Topics



Leave a reply



Submit