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

时间:2016-07-15 13:38:06

标签: swift2 grand-central-dispatch tvos dispatch-after

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()不适用于更多的异步任务。

提前致谢!

1 个答案:

答案 0 :(得分:2)

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

当您使用dispatch_after时,它会创建一个调度计时器,其余地为start / when的10%。请参阅Apple github libdispatch 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定时器释放块(避免强引用周期),当定时器是取消/完成。这是一个不优雅的解决方案,恕我直言。

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

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

  3. 对于Swift 2.3的演绎,请参阅previous version of this answer