Swift: DispatchQueue.global(qos: .userInitiated).async 是否锁定了主线程?

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

我目前正在尝试解决 0x8BADF00D

faultingThread 是 0,我认为这是主线程。但是,我认为我正在做的大量工作实际上并没有在主线程中进行。

在主线程上执行 运行 的函数中我执行

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

我的逻辑有明显错误吗?我认为 .userInitiated 会离开主线程,尤其是在 .async.

“异常”:{“代码”:“0x0000000000000000,0x0000000000000000”,“rawCodes”:[0,0],“类型”:“EXC_CRASH”,“信号”:“SIGKILL”} , “终止”:{“标志”:6,“代码”:2343432205,“命名空间”:“FRONTBOARD”,“原因”:[“:579 耗尽实际(挂钟)时间允许 10.00 秒”,“ProcessVisibility:背景”,“ProcessState:运行”,“WatchdogEvent:场景更新” "WatchdogVisibility: Background","WatchdogCPUStatistics: (",""Elapsed total CPU time (seconds): 21.740 (user 21.740, system 0.000), 99% CPU" ",","应用程序已用 CPU 时间(秒):9.832,45% CPU"",") reportType:CrashLog maxTerminationResistance:Interactive>"]}, “故障线程”:0,

QoS 决定了系统仅调度任务执行的优先级。

您始终可以使用 Thread.current:

检查当前线程
DispatchQueue.global(qos: .userInitiated).async {
    print(Thread.current.description)
}

输出:

<NSThread: 0x6000005bde00>{number = 7, name = (null)}`

主线程只有 number = 0name = main,因此您的代码在后台线程上运行,您可以在其上做繁重的工作。

此处的模式是正确的(尽管建议更合理的 QoS)。您的看门狗问题(主线程被阻塞)在别处。但是将昂贵的进程调度到后台队列,然后将 UI/model 更新调度回主队列,才是正确的过程。


提醒一句:这种一般模式 可以 在某些情况下阻塞主线程。具体来说,如果你有线程爆炸(例如,超过 64 个任务被分配到那个全局队列),一个可以 block/deadlock。 GCD 有一个非常有限的线程池(此时为 64),如果你超过了这个,后续派发到该队列的尝试将被阻塞,直到队列被释放。这通常只是在您使用锁、信号量或以其他方式等待时才会出现的问题。不幸的是,您的代码片段中没有足够的内容供我们诊断此特定案例中的问题。

所以,如果你有线程爆炸,你应该重构来约束最大并发度。操作队列有 maxConcurrentOperation 来促进这一点。 GCD 有 concurrentPerform,在任何给定时间都不会超过 运行 允许的最大线程数。 Swift 5.5 协作线程池也将并行度限制在一个合理的范围内。 Combine 有“max publishers”来控制并发度。过去,我们可能使用非零信号量来限制并发度(尽管现在我们倾向于使用上述现代解决方案之一)。有很多方法可以减轻线程爆炸。

所有这些都假设问题出在线程爆炸上(如果嵌套 async 分派阻塞,这可能是罪魁祸首)。如果你没有线程爆炸,那么你只是有其他完全不相关的东西阻塞了主线程。