DispatchQueue deadlock clarification needed - ios

Look at these two slightly similar cases:
case 1:
func test() {
DispatchQueue.global().sync {
print("a")
DispatchQueue.main.sync {
print("b")
}
}
}
and
case 2:
func test() {
DispatchQueue.main.async {
print("a")
DispatchQueue.main.sync {
print("b")
}
}
}
In the first case the code crashes with
EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP,...
the explaination for that acc to me is that in the first case the task is submitted synchronoulsy on the global queue using DispatchQueue.global().sync . Given the the function test() itself was called from the main queue, the main queue will wait for the task added in *DispatchQueue.global().sync to finish. However, it encounter another task added synchronously to the main queue DispatchQueue.main.sync { print("b") } . Now the task that's running on the main queue is blocked waiting for this new task that's again running on the main queue to complete. Hence, deadlock which causes the crash.
In the second case, it is somewhat similar to the first, However, there is no crash even though the task print("b") is added to main serial queue synchronoulsy (which is inside test() method which is itself called from the main queue) but it is still causing deadlock which is evident from the fact that "a" is printed but "b" is not printed in the second case.
I need to understand why there was no crash in this case (on playground)

If you jump to the top of that stack trace resulting from that EXC_BAD_INSTRUCTION and it will tell you precisely what the issue was:
Why your second example did not generate a EXC_BAD_INSTRUCTION, I cannot say, as it does on my computer.
That having been said, most deadlocks simply do not generate a nice EXC_BAD_INSTRUCTION for you. Often it just freezes. Why your second was not caught by the same diagnostic is unclear. But you should not be relying upon this EXC_BAD_INSTRUCTION, anyway, as it only catches one particular deadlock risk...

Related

Completion handler being called on background Thread instead of main UI thread in iOS

I have a networking class that does my fetching of data from the server. In the completion handler of that class, it looks something like this:
func fetchData(url: URL, completion: #escaping (Result<Data, MyError>) -> Void) {
let request = URLRequest(url: url)
fetch(request: request) { (result: Result<Data, MyError>) in
switch result {
case .success(let response):
DispatchQueue.main.async {
completion(.success(response))
}
case .failure(let error):
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
If I call this fetchData method from my ViewController, I get the callback on the main thread and I don't have to reload my collection view on the main thread. I then tried adding a view model for my view controller. So the flow looks more like:
ViewController -> ViewModel (fetchData) -> Networking (fetchData)
where basically each class just calls a method that looks exactly like the above fetchData method, passing the completion upwards. In ViewController, do I need to check again that I'm on the main thread. Could iOS switch threads during these calls? I ask because I did get a warning about updating the UI was not called on the main thread one time. But I'm not sure if that was a false negative from this call since I have other networking calls to fetch images, and maybe I messed something else up elsewhere. But basically, I'm just asking if I don't do any other GCD type tasks, but only use completion handlers and bubble up the completion from the single networking call that calls back on the main thread, do I need to check again somewhere up the chain (like in the ViewController).
You haven't provided the code fo "these calls", so it isn't possible to say whether code will be dispatched on another queue, however, the system doesn't arbitrarily switch to another queue while executing code. You need to explicitly or implicitly dispatch onto another queue. Your code above contains an explicit dispatch onto the main queue and an implicit dispatch onto another queue when you call fetch (Somewhere in that code will be an implicit dispatch onto another queue, perhaps in code where you can't see the source).
As a simple answer to your question, if you dispatch onto the main queue in the completion handler shown and none of the other code called "further up" performs asynchronous work or explicitly dispatches onto a queue other than the main queue you can be certain that execution will continue on the main queue.
Also, you can simplify your code by simply calling the upstream completion handler directly:
func fetchData(url: URL, completion: #escaping (Result<Data, MyError>) -> Void) {
let request = URLRequest(url: url)
fetch(request: request) { (result: Result<Data, MyError>) in
DispatchQueue.main.async {
completion(response))
}
}
}
When designing your code you should adopt one of two approaches and stick to it:
Dispatch onto the main queue early. This approach is often taken by frameworks that may well be consumed by someone else; For example AFNetworking explicitly documents that completion handlers are dispatched onto the main queue so you don't need to worry about it. The disadvantage of this approach is that programmers may not read the documentation and may dispatch onto the main queue defensively, leading to double asynchronous dispatch or they may not be updating the UI and don't need main thread execution. This is an overhead but unlikely to be a major issue.
Never dispatch onto the main queue and rely on the calling code to dispatch if it needs to do so. This approach may be more common where all of the code is part of one solution and the programmer "knows" that they ultimately need to dispatch onto the main queue. The advantage of this approach is that you defer (and potentially avoid entirely if it isn't required) dispatching work to the main queue. The disadvantage is that if you forget to do it you will get warnings and main thread violations
if you're talking about this:
func fetchData(url: URL) { result in
print(result) // <-- This on main thread and should not cause any warnings
}
If you're certain that's what's happening then it's a false positive. But I highly doubt it. I've never seen it malfunction. You can easily use the Main Thread Checker and detect mistakes.
Aside from that normally functions shouldn't dictate the completionHandler's thread. ie it's on the caller to dispatch the thread. I mean if you ever wanted to dispatch this to another thread, then you'd be dispatching it twice which isn't ideal.

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

Calling main.async while on a background thread to run UI code that should be handled by the main thread appears to be a standard practise.
When we call main.async while on a background thread and the main thread is busy with normal code that's not used any GCD calls (I assume this is equivalent to main.sync?) when is this code executed?
Is the regular main.sync code executed first or will our main.async code be executed and how does this work? How can a single queue execute asynchronous and synchronous code at the same time?
Playground Example: (The A array is printed but B array isn't)
let a = "a"
let b = "b"
let aArray = [a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a]
let bArray = [b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b]
for letter in aArray {
print(letter)
}
DispatchQueue.global().async {
print("Entered background thread")
DispatchQueue.main.async {
print("Left background thread")
for letter in bArray {
print(letter)
}
}
}
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.

GCD Main Thread Crash Issue (Explanation Needed)?

why do this piece of code causes crash ?
DispatchQueue.main.sync {
// Operation To Perform
}
why we have to write this way :-
DispatchQueue.global().async(execute: {
print("test")
DispatchQueue.main.sync{
print("main thread")
}
})
and when we write code in CellForRowAt or any other method in which thread it goes main or global on how it works sync or async way ?
According to Apple, attempting to synchronously executing a work item on main queue results into a dead-lock.
So writing DispatchQueue.main.sync {} can lead to deadlock condition as all the UI operations performed by app is performed on main queue unless we manually switch some task on the background queue. This also answer your question regarding on which thread CellForRowAt is called. All the methods related to UI operation or UIkit are called from main thread
Performing a task synchronously means blocking a thread until the task is not completed and in this case you are attempting to block main thread on which the system / app would be already performing some task and that can lead to deadlock. Blocking main thread is not at all recommended and thats why we need to switch asynchronously to a background thread so that main thread is not blocked.
To read more you can visit the following link:
https://developer.apple.com/documentation/dispatch
Why crash In Short
DispatchQueue.main.sync {
// Operation To Perform
}
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.
For Second block : You are creating global queue and then you are getting main queue so now there is no dead lock
If you have ever used semaphore which has same issue if you don't take care
it has two methods wait and signal with wait if you block main thread then your code will never executed.
hope it is helpful
DispatchQueue.main.sync {
// Operation To Perform
}
Calling sync on a serial queue (like main) that you're already on will cause a deadlock. The first process can't finish because it's waiting for the second process to finish, which can't finish because it's waiting for the first to finish etc.
DispatchQueue.global().async(execute: {
print("test")
DispatchQueue.main.sync{
print("main thread")
}
})
Calling sync on the main thread from here works as you're moving the task to the global() queue.
There's a great 2 part GCD tutorial on raywenderlich.com which I encourage you to read https://www.raywenderlich.com/148513/grand-central-dispatch-tutorial-swift-3-part-1.

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

In Swift, I used this kind of pattern sometimes.
DispatchQueue.global().async {
// do stuff in background, concurrent thread
DispatchQueue.main.sync {
// update UI
}
}
The purpose of this pattern is clear. Do time consuming calculation in global thread so UI is not locked and update UI in main thread after calculation is done.
What if there's nothing to calculate? I just found a logic in my project which
//A
DispatchQueue.main.sync {
// do something
}
crashes but
// B
DispatchQueue.global().async {
DispatchQueue.main.sync {
// do something
}
}
doesn't crash.
How are they different? And Is case B different with just this?
// C
DispatchQueue.main.async {
// do something
}
And one more question. I know main thread is serial queue, but if I run multiple code block in multiple main.async, it works like concurrent queue.
DispatchQueue.main.async {
// do A
}
DispatchQueue.main.async {
// do B
}
If main thread is really a serial queue, how can they run simultaneously? If it is just a time slicing than how are they different with global concurrent queue other than main thread can update UI?
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 + Async: How to execute a callback on the same thread where it was created?

Using the Async library, a simple pattern to do work on a background thread might look like this:
// Assume we start on the main thread
let onResultComplete: (result: ResultType) -> Void = { result in
Async.main {
// Code to handle one result at a time on the main thread
}
}
Async.background {
doCalculationsThatProduceManyResults(onEachResultComplete: onResultComplete)
}
Now consider this scenario, where the code is already being executed on a background thread:
// Assume we start on some "unknown background thread"
let onResultComplete: (result: ResultType) -> Void = { result in
Async.??? {
// Code to handle one result at a time on the "unknown background thread"
}
}
Async.background {
doCalculationsThatProduceManyResults(onEachResultComplete: onResultComplete)
}
How can I force the closure onResultComplete to be run on the same unknown background thread from where I called Async.background?
I'm open to any suggestions that use GCD methods.
On iOS or macOS, if some code executes on an unknown thread or dispatch queue (say: "execution context"), there's no means to reliable obtain some "handle" for it - well, unless this is the main thread.
So, the solution to your problem is to first create or obtain a known execution context (aka dispatch queue or thread) and execute your code here. Then, in the continuation (aka completion handler), explicitly dispatch back to this same execution context again and continue with your code.
Don't call Async.anything. Simply run the code in-line (assuming the Async library calls it's completion block on the same thread where the calculations are run.
Your question is specific to the Async library, so you should put that in your title and add a tag for it. (I've never used it, so I don't know the specifics of how it works.)

Resources