dispatch_get_main_queue() 不能顺利 运行 新的异步作业

dispatch_get_main_queue() doesn't run new async jobs smoothly

let dispatchGroup = dispatch_group_create()
let now = DISPATCH_TIME_NOW

for i in 0..<1000 {
    dispatch_group_enter(dispatchGroup)

    // Do some async tasks
    let delay = dispatch_time(now, Int64(Double(i) * 0.1 * Double(NSEC_PER_SEC)))

    dispatch_after(delay, dispatch_get_main_queue(), {
        print(i)
        dispatch_group_leave(dispatchGroup)
    })
}

print 语句可以顺利打印前 15-20 个数字,但是当 i 变大时,print 语句打印东西会很慢。我在 dispatch_after 里面有更复杂的逻辑,我注意到处理非常缓慢,这就是我写这个测试的原因。

我可以配置缓冲区大小或其他属性吗?似乎 dispatch_get_main_queue() 不能很好地处理更多的异步任务。

提前致谢!

问题不在于 dispatch_get_main_queue()。 (如果您使用不同的队列,您会注意到相同的行为。)问题出在 dispatch_after().

当您使用 dispatch_after 时,它会创建一个具有 start/when 的 10% 回旋余地的调度计时器。见苹果githublibdispatch source。最终效果是当这些计时器 (start ± 10% leeway) 重叠时,它可能会开始合并它们。当它们结合在一起时,它们会以“成群”的方式开火,其中一束紧接着另一束开火,然后在到达下一组之前稍有延迟。

有几个解决方案,都需要退出一系列 dispatch_after 调用:

  1. 您可以手动构建计时器,强制 DispatchSource.TimerFlag.strict 禁用合并:

     let group = DispatchGroup()
     let queue = DispatchQueue.main
    
     let start = CACurrentMediaTime()
    
     os_log("start")
    
     for i in 0 ..< 1000 {
         group.enter()
    
         let timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue) // use `.strict` to avoid coalescing
         timer.setEventHandler {
             timer.cancel()       // reference timer so it has strong reference until the handler is called
             os_log("%d", i)
             group.leave()
         }
         timer.schedule(deadline: .now() + Double(i) * 0.1)
         timer.resume()
     }
    
     group.notify(queue: .main) {
         let elapsed = CACurrentMediaTime() - start
         os_log("all done %.1f", elapsed)
     }
    

    就我个人而言,我不喜欢闭包中对 timer 的引用,但是您需要保持对它的一些强引用,直到计时器触发,并且 GCD 计时器在定时器是 canceled/finishes。这是不优雅的解决方案,恕我直言。

  2. 仅安排每 0.1 秒触发一次的单个重复计时器效率更高:

     var timer: DispatchSourceTimer?    // note this is property to make sure we keep strong reference 
    
     func startTimer() {
         let queue = DispatchQueue.main
    
         let start = CACurrentMediaTime()
    
         var counter = 0
    
         // Do some async tasks
    
         timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue)
         timer!.setEventHandler { [weak self] in
             guard counter < 1000 else {
                 self?.timer?.cancel()
                 self?.timer = nil
                 let elapsed = CACurrentMediaTime() - start
                 os_log("all done %.1f", elapsed)
                 return
             }
             os_log("%d", counter)
             counter += 1
         }
         timer!.schedule(deadline: .now(), repeating: 0.05)
         timer!.resume()
     }
    

    这不仅解决了合并问题,而且效率更高。

对于 Swift 2.3 版本,请参阅 previous version of this answer