2

私は自分のアプリで次のサイクルを持っています

var maxIterations: Int = 0

func calculatePoint(cn: Complex) -> Int {

    let threshold: Double = 2
    var z: Complex = .init(re: 0, im: 0)
    var z2: Complex = .init(re: 0, im: 0)
    var iteration: Int = 0

    repeat {
        z2 = self.pow2ForComplex(cn: z)
        z.re = z2.re + cn.re
        z.im = z2.im + cn.im
        iteration += 1
    } while self.absForComplex(cn: z) <= threshold && iteration < self.maxIterations

    return iteration
}

サイクルの実行中に虹の輪が表示されます。アプリがまだ UI アクションに応答していることを管理するにはどうすればよいですか? サイクルの実行中に、更新されていないコードの別の部分で NSProgressIndicator を更新したことに注意してください (進行状況は表示されません)。私はそれが派遣と関係があるのではないかと疑っていますが、私はそれについてかなり「グリーン」です. どんな助けにも感謝します。ありがとう。

4

1 に答える 1

6

何かを非同期にディスパッチする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.

Martin は、あなたがマンデルブロ集合を計算していると推測しました。その場合、各ポイントの計算をグローバル キューにディスパッチすることはお勧めできません (これらのグローバル キューはブロックをワーカー スレッドにディスパッチしますが、それらのワーカー スレッドは非常に限られているためです)。

これらのグローバル キュー ワーカー スレッドをすべて使い果たしたくない場合、1 つの簡単な選択肢はasync、個々のポイントを計算するルーチンから呼び出しを取り除き、すべての複雑な値を反復処理するルーチン全体をバックグラウンドにディスパッチすることです。スレッド:

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複雑な計算のループを実行するときの 1 つのアプローチは、使用することですdispatch_apply(現在concurrentPerformは Swift 3 で呼び出されています)。これはforループに似ていますが、それぞれのループを相互に同時に実行します (ただし、最後に、これらの同時ループがすべて終了するのを待ちます)。これを行うには、外側のforループをconcurrentPerform次のように置き換えます。

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) は、そのループのさまざまな反復を同時に実行しますが、デバイスの機能に合わせて同時スレッドの数を自動的に最適化します。for私の MacBook Pro では、これにより計算が単純なループより 4.8 倍速くなりました。ここでもすべてをグローバル キューにディスパッチしますが (concurrentPerform同期的に実行され、メイン スレッドで低速な同期計算を実行したくないため)、concurrentPerform計算は並列で実行します。forこれは、GCD ワーカー スレッドを使い果たすことがないように、ループ内で同時実行を楽しむための優れた方法です。

マンデルブローセット


ところで、あなたは を更新していると言いましたNSProgressIndicator。理想的には、すべてのピクセルが処理されるたびに更新する必要がありますが、そうすると、UI が未処理になり、これらすべての更新についていくことができなくなる可能性があります。UI が進行状況インジケーターのすべての更新に追いつくことができるように、最終結果の速度が遅くなります。

解決策は、進行状況の更新から UI の更新を切り離すことです。各ピクセルが更新されるたびにバックグラウンド計算で通知する必要がありますが、進行状況インジケーターを更新するたびに、「最後にチェックしてから計算されたピクセル数で進行状況を更新してください」と効果的に言う必要があります。これを行うには面倒な手動の手法がありますが、GCD は非常に洗練されたソリューション、ディスパッチ ソース、またはより具体的にはDispatchSourceUserDataAdd.

したがって、ディスパッチ ソースのプロパティとカウンタを定義して、これまでに処理されたピクセル数を追跡します。

let source = DispatchSource.makeUserDataAddSource(queue: .main)
var pixelsProcessed: UInt = 0

次に、進行状況インジケーターを更新するディスパッチ ソースのイベント ハンドラーを設定します。

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 が更新されますが、更新のキューで未処理になることはありません。ディスパッチ ソースは、これらのadd呼び出しを合体させます。

于 2016-10-09T22:45:02.650 に答える