1
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出力できますが、大きくなると、出力が遅くなります。内部にはもっと複雑なロジックがdispatch_afterあり、処理が非常に遅いことに気付きました。それがこのテストを書いた理由です。

設定できるバッファ サイズやその他のプロパティはありますか? dispatch_get_main_queue()より多くの非同期タスクではうまく機能しないようです。

前もって感謝します!

4

1 に答える 1

2

問題はありませんdispatch_get_main_queue()。(別のキューを使用すると、同じ動作に気付くでしょう。) 問題は にありdispatch_after()ます。

を使用すると、 /dispatch_afterの 10% の余裕を持ったディスパッチ タイマーが作成されます。Apple github libdispatch ソースを参照してください。最終的な影響として、これらのタイマー ( ± 10% ) がオーバーラップすると、それらの合体が開始される可能性があります。それらが合体すると、それらは「まとまった」方法で発砲するように見えます。それらの束はすぐに次々と発砲し、次の束に到達する前に少し遅れて発砲します。startwhenstartleeway

いくつかの解決策がありますが、すべて一連の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 タイマーはブロックを解放します (強い参照サイクルを回避します)。これは洗練されていないソリューションです、IMHO。

  1. 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 レンディションについては、この回答の以前のバージョンを参照してください。

于 2016-07-16T01:12:58.657 に答える