何かを非同期でディスパッチするには、該当するキューのasync
を呼び出します。たとえば、このメソッドを変更してグローバルバックグラウンドキューで計算を行い、その結果をメインキューに戻すことができます。あなたは、あなたが計算が行われたときに、非同期メソッドを呼び出します完了ハンドラのクロージャを使用して、すぐに結果を返すからシフトすることを行う方法、で:
func calculatePoint(_ cn: Complex, completionHandler: @escaping (Int) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
// do your complicated calculation here which calculates `iteration`
DispatchQueue.main.async {
completionHandler(iteration)
}
}
}
そして、あなたはそうのようにそれを呼びたいです:
// start NSProgressIndicator here
calculatePoint(point) { iterations in
// use iterations here, noting that this is called asynchronously (i.e. later)
// stop NSProgressIndicator here
}
// don't use iterations here, because the above closure is likely not yet done by the time we get here;
// we'll get here almost immediately, but the above completion handler is called when the asynchronous
// calculation is done.
マーティンはあなたがマンデルブロ集合を計算していると推測しています。その場合、各点の計算をグローバルキューにディスパッチすることはお勧めできません(これらのグローバルキューはブロックをワーカースレッドにディスパッチしますが、それらのワーカースレッドはかなり制限されているため)。
これらのグローバルキューワーカースレッドをすべて使い果たしないようにするには、簡単な選択肢の1つは、個々のポイントを計算するルーチンから呼び出すことです。すべてのルーチンを繰り返し実行するルーチン全体をディスパッチします。
DispatchQueue.global(qos: .userInitiated).async {
for row in 0 ..< height {
for column in 0 ..< width {
let c = ...
let m = self.mandelbrotValue(c)
pixelBuffer[row * width + column] = self.color(for: m)
}
}
let outputCGImage = context.makeImage()!
DispatchQueue.main.async {
completionHandler(NSImage(cgImage: outputCGImage, size: NSSize(width: width, height: height)))
}
}
「メインスレッドをそれを降りる」と「ワーカースレッドを使用していない」の問題を解決しますが、今、私たちはあまりにも多くの労働者を使用してから振ってきました:バックグラウンドスレッドへの複雑な値1つのワーカースレッドのみを使用し、デバイスを完全に利用しないようにします。私たちは本当に多くの計算を並行して(ワーカースレッドを使い果たしていなくても)実行したいと思っています。
複雑な計算のためにfor
ループを実行する場合、dispatch_apply
(現在はSwift 3ではconcurrentPerform
と呼ばれています)を使用することです。これはループのようなものですが、それぞれのループを同時に実行します(ただし、最後にすべての並列ループが終了するのを待ちます)。これを行うには、concurrentPerform
と外側のfor
ループを置き換える:
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.concurrentPerform(iterations: height) { row in
for column in 0 ..< width {
let c = ...
let m = self.mandelbrotValue(c)
pixelBuffer[row * width + column] = self.color(for: m)
}
}
let outputCGImage = context.makeImage()!
DispatchQueue.main.async {
completionHandler(NSImage(cgImage: outputCGImage, size: NSSize(width: width, height: height)))
}
}
concurrentPerform
(旧dispatch_apply
として知られている)、同時にそのループの様々な反復を実行しますが、それは自動的に機能の同時実行スレッド数を最適化しますあなたのデバイスの私のMacBook Proでは、単純なfor
ループよりも4.8倍高速でした。ただし、私はまだ全体をグローバルキューに送ります(concurrentPerform
は同期的に実行されるため、メインスレッドでは遅い同期計算を実行したくないため)。concurrentPerform
は計算を並行して実行します。 for
ループで同時実行性を楽しむのに最適な方法です.GCDワーカースレッドを使い切ることはありません。ところで
、あなたがNSProgressIndicator
を更新していると述べました。理想的には、すべてのピクセルが処理されるたびに更新したいのですが、そうするとUIがバックログになり、これらの更新すべてに追いつくことができません。最終的な結果が遅くなり、UIがすべての進行状況インジケータの更新を把握できるようになります。
解決策は、進行状況の更新からUI更新を切り離すことです。各ピクセルが更新されたときにバックグラウンド計算で通知する必要がありますが、「OK、多くのピクセルが前回チェックした後に進捗状況を更新する」と効果的に表示されるたびに、進行状況インジケーターを更新する必要があります。それを行う手作業のテクニックは面倒ですが、GCDは非常に洗練されたソリューション、ディスパッチソース、具体的にはDispatchSourceUserDataAdd
を提供しています。
そして、進捗状況を更新ディスパッチソース用のイベントハンドラを設定します。
だから、派遣元とこれまでに処理されたどのように多くのピクセルを追跡するカウンタのプロパティを定義インジケーター:
source.setEventHandler() { [unowned self] in
self.pixelsProcessed += self.source.data
self.progressIndicator.doubleValue = Double(self.pixelsProcessed)/Double(width * height)
}
source.resume()
そして、あなたはピクセルを処理として、次のことができ、単にadd
バックグラウンドスレッドからあなたの元へ:
DispatchQueue.concurrentPerform(iterations: height) { row in
for column in 0 ..< width {
let c = ...
let m = self.mandelbrotValue(for: c)
pixelBuffer[row * width + column] = self.color(for: m)
self.source.add(data: 1)
}
}
これを行うと、できるだけ頻度の高いUIが更新されますが、更新キューではバックログに記録されることはありません。ディスパッチソースはこれらのメッセージを統合します。
長い計算をバックグラウンドで実行する必要があります。 – rmaddy
完全には無関係ですが、 'Complex'計算のいくつかを' struct'/'class'に移すことをお勧めします。そして、より直感的なマンデルブロアルゴリズムになります:https://gist.github.com/robertmryan/ 91536bf75e46cbdaed92c37e99fdbe7d – Rob