Quoting apple docs
.sync
This function submits a block to the specified dispatch queue for
synchronous execution. Unlike dispatch_async(::), this function does
not return until the block has finished
Which means when you first called background.sync { control was on main thread which belongs to a main queue (which is a serialized queue), as soon as the statement background.sync { was executed, controlled stopped at main queue and its now waiting for the block to to finish execution
But inside background.sync { you access the main queue again by referring DispatchQueue.main.sync { and submit another block for synchronous execution which simply prints "Hello from background sync", but the control is already waiting on main queue to return from background.sync { hence you ended up creating a deadlock.
Main Queue is waiting for control to return from background queue which in turn is waiting for Main queue to finish the execution of print statement :|
In fact apple specifically mentions this usecase in its Description
Calling this function and targeting the current queue results in
deadlock.
Additional info:
By accessing main queue inside background queue you simply established circular dependency indirectly, if you really wanna test the above statement you can do it simply as
let background = DispatchQueue(label: "backgroundQueue",
qos: .background,
attributes: [],
autoreleaseFrequency: .inherit,
target: nil)
background.sync {
background.sync {
print("Hello from background sync")
}
}
Clearly you are referring background queue inside background.sync which will cause deadlock, which is what apple docs specifies in its description. Your case was slightly different in a sense that you referred to main queue causing the deadlock indirectly
How using async in any one of those statements breaks the dealock?
Now you can use async in either background.async { or in DispatchQueue.main.async and deadlock will break essentially (I am not suggesting which one is correct here, which is correct depends on your need and what are you trying to accomplish, but to break deadlock you can use async in any one of those dispatch statements and you will be fine)
I will just explain why deadlock will break in only one scenario ( You can infer the solution for other case obviously). Let's just say you use
background.sync {
DispatchQueue.main.async {
print("Hello from background sync")
}
}
Now main queue is waiting for the block to finish execution which you submitted to background queue for synchronous execution using background.sync and inside background.sync you access main queue again using DispatchQueue.main but this time you submit your block for the asynchronous execution. Hence control will not wait for the block to finish the execution and instead returns immediately. Because there are no other statements in block you submitted to background queue, it marks the completion of task, hence control returns to main queue. Now main queue does processes tasks submitted and whenever it its time to process your print("Hello from background sync") block it prints it.