Чтобы отправить что-то асинхронно, вызовите 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.
Мартин предположил, что вы вычисляете множество Мандельброта. Если это так, отправка вычисления каждой точки в глобальную очередь не является хорошей идеей (потому что эти глобальные очереди отправляют свои блоки рабочим потокам, но эти рабочие потоки весьма ограничены).
Если вы хотите избежать использования всех этих глобальных рабочих потоков очереди, один простой выбор - взять вызов 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)))
}
}
Это решает проблемы «убрать из основного потока» и «не использовать рабочие потоки», но теперь мы перешли от использования слишком большого количества рабочих потоков к использованию только одного рабочего потока, не полностью используя устройство. . Мы действительно хотим выполнять столько же вычислений параллельно (при этом не истощая рабочие потоки).
Один из подходов при выполнении цикла for
для сложных вычислений - использовать 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
) будет выполнять различные итерации этого цикла одновременно, но он автоматически оптимизирует количество параллельных потоков для возможностей вашего устройства. На моем MacBook Pro это сделало расчет в 4,8 раза быстрее, чем простой цикл for
. Обратите внимание: я все еще отправляю все это в глобальную очередь (потому что concurrentPerform
выполняется синхронно, и мы никогда не хотим выполнять медленные синхронные вычисления в основном потоке), но concurrentPerform
будет выполнять вычисления параллельно. Это отличный способ реализовать параллелизм в цикле for
, не исчерпывая рабочие потоки GCD.
Кстати, вы упомянули, что обновляете NSProgressIndicator
. В идеале вы хотите обновлять его по мере обработки каждого пикселя, но если вы это сделаете, пользовательский интерфейс может задерживаться, не успевая за всеми этими обновлениями. В конечном итоге вы замедляете конечный результат, чтобы пользовательский интерфейс мог успевать за всеми этими обновлениями индикаторов прогресса.
Решение состоит в том, чтобы отделить обновление пользовательского интерфейса от обновлений хода выполнения. Вы хотите, чтобы фоновые вычисления сообщали вам, как обновляется каждый пиксель, но вы хотите, чтобы индикатор прогресса обновлялся, каждый раз эффективно говоря: «Хорошо, обновите прогресс, указав, сколько пикселей было вычислено с момента последней проверки». Для этого существуют громоздкие ручные методы, но 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)
}
}
Если вы это сделаете, он будет обновлять пользовательский интерфейс с максимально возможной частотой, но никогда не будет задерживаться в очереди обновлений. Источник отправки объединит эти add
звонки за вас.
person
Rob
schedule
09.10.2016
Complex
вычисления в ваш _2 _ / _ 3_, и тогда вы получите более интуитивный алгоритм Мандельброта: gist.github.com/robertmryan/91536bf75e46cbdaed92c37e99fdbe7d - person Rob   schedule 10.10.2016