Search code examples
swiftgrand-central-dispatch

Swift: Does DispatchQueue.global(qos: .userInitiated).async lock the main thread?


I am currently trying to resolve an 0x8BADF00D.

faultingThread is 0 which I suppose to be the main thread. However, I don't think the bulk work that I am doing actually goes down in the main thread.

In a function that does run on the main thread I do

DispatchQueue.global(qos: .userInitiated).async {
 // ... heavy work

 // and after the work is done I do
  DispatchQueue.main.asyncAfter(deadline: .now()+1.0) {
    // ... displaying heavy work
  }
}

Is there an obvious fault in my logic? I thought the .userInitiated would leave the main thread, especially on .async.

"exception" : {"codes":"0x0000000000000000, 0x0000000000000000","rawCodes":[0,0],"type":"EXC_CRASH","signal":"SIGKILL"}, "termination" : {"flags":6,"code":2343432205,"namespace":"FRONTBOARD","reasons":["<RBSTerminateContext| domain:10 code:0x8BADF00D explanation:scene-update watchdog transgression: application<com.test>:579 exhausted real (wall clock) time allowance of 10.00 seconds","ProcessVisibility: Background","ProcessState: Running","WatchdogEvent: scene-update","WatchdogVisibility: Background","WatchdogCPUStatistics: (",""Elapsed total CPU time (seconds): 21.740 (user 21.740, system 0.000), 99% CPU",",""Elapsed application CPU time (seconds): 9.832, 45% CPU"",") reportType:CrashLog maxTerminationResistance:Interactive>"]}, "faultingThread" : 0,


Solution

  • The pattern here is correct (though a more reasonable QoS would be well advised). Your watchdog problem, where the main thread was blocked, rests elsewhere. But dispatching expensive process to a background queue, and then dispatching UI/model updates back to the main queue, is the correct procedure.


    A word of caution: This general pattern can block the main thread in certain scenarios. Specifically, if you had thread explosion (e.g., more than 64 tasks dispatched to that global queue), one can block/deadlock. GCD has a very limited thread pool (64 at this point) and if you exceed this, subsequent attempts to dispatch to that queue will block until the queue is freed up. This generally is only a problem where you are using locks, semaphores, or otherwise waiting. Unfortunately, there is not enough in your code snippet for us to diagnose the problem in this particular case.

    So, if you have a thread explosion, you should refactor to constrain the maximum degree of concurrency. Operation queues have maxConcurrentOperation to facilitate that. GCD has concurrentPerform which will not exceed the maximum number of threads permitted to run at any given time. The Swift 5.5 cooperative thread pool also constrains parallelism to a reasonable limit. Combine has “max publishers” for controlling the degree of concurrency. In past, we might have used non-zero semaphores to constrain the degree of concurrency (though nowadays we would tend to use one of the aforementioned contemporary solutions). There are lots of approaches that mitigate thread explosion.

    All of this assumes that the problem rests with thread explosion (which is the likely culprit if the nested async dispatches are blocking). If you do not have thread explosion, then you simply have something else, completely unrelated, blocking the main thread.