What Does Main.Sync in Global().Async Mean

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.

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.

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.

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

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.



Related Topics



Leave a reply



Submit