我在我的应用程序中跟随循环
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,但没有更新(进度未显示)。 我怀疑它与调度有关但我非常&#34;绿色&#34;接着就,随即。我非常感谢任何帮助。 感谢。
答案 0 :(得分:3)
要异步调度某些内容,请在相应的队列上调用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.
马丁猜测你正在计算一个Mandelbrot集。如果是这样,将每个点的计算分派到全局队列不是一个好主意(因为这些全局队列将它们的块分派给工作线程,但这些工作线程非常有限)。
如果你想避免使用所有这些全局队列工作线程,一个简单的选择是从你的例程中取出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)))
}
}
这解决了&#34;从主线程中解脱出来&#34;并且不会消耗工作线程&#34;问题,但现在我们已经从使用过多的工作线程转向仅使用一个工作线程,而没有充分利用该设备。我们真的希望并行执行尽可能多的计算(而不是耗尽工作线程)。
在进行复杂计算的for
循环时,一种方法是使用dispatch_apply
(现在在Swift 3中称为concurrentPerform
)。这就像一个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上,这使计算速度比简单的for
循环快4.8倍。注意,我仍然将整个事件分配到全局队列(因为concurrentPerform
同步运行,我们永远不想在主线程上执行慢速,同步计算),但concurrentPerform
将并行运行计算。这是一种在for
循环中享受并发性的好方法,使您不会耗尽GCD工作线程。
顺便提一下,您提到您正在更新NSProgressIndicator
。理想情况下,您希望在处理每个像素时更新它,但如果这样做,UI可能会积压,无法跟上所有这些更新。您最终会放慢最终结果的速度,以便UI能够赶上所有这些进度指示器更新。
解决方案是将UI更新与进度更新分离。您希望背景计算在每个像素更新时通知您,但您希望更新进度指示器,每次有效地说&#34;确定,更新进度,但是自上次检查后计算了多少像素&# 34 ;.有一些繁琐的手工技术可以做到这一点,但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
电话。