Swift3 长周期块的应用

Swift3 长周期块的应用,swift3,dispatch-async,nsprogressindicator,Swift3,Dispatch Async,Nsprogressindicator,我在我的应用程序中有以下循环 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 {

我在我的应用程序中有以下循环

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
}
var maxIterations:Int=0
func calculatePoint(cn:Complex)->Int{
let阈值:双=2
var z:Complex=.init(re:0,im:0)
var z2:Complex=.init(re:0,im:0)
变量迭代:Int=0
重复{
z2=自功率2复合(cn:z)
z、 re=z2.re+cn.re
z、 im=z2.im+cn.im
迭代次数+=1

}而self.absForComplex(cn:z)若要异步调度某些内容,请在相应的队列上调用
async
。例如,您可以将此方法更改为在全局后台队列上执行计算,然后将结果报告回主队列。顺便说一句,在执行此操作时,您将从立即返回结果转换为使用完成处理程序closure完成计算时异步方法将调用的对象:

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推测您正在计算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)))
    }
}
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)
    }
}
concurrentPerform
(以前称为
dispatch\u apply
)将同时执行该循环的各种迭代,但它将自动优化设备功能的并发线程数。在我的MacBook Pro上,这使计算速度比简单的
for
循环快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()
然后,在处理像素时,只需从背景线程向源添加

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)))
    }
}
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)
    }
}
DispatchQueue.concurrentPerform(迭代次数:高度){中的行
对于0..<宽度中的列{
让c=。。。
设m=self.mandelbrotValue(for:c)
pixelBuffer[行*宽+列]=self.color(for:m)
self.source.add(数据:1)
}
}

如果您这样做,它将以尽可能高的频率更新UI,但它不会因更新队列而积压。调度源将为您合并这些
添加
调用。

您需要在后台运行长时间的计算。完全不相关,但我可能建议移动一些
复杂的
结构
/
中进行计算,然后得到更直观的Mandelbrot算法:似乎OP想要计算/绘制“Mandelbrot集”M,然后只需要迭代次数,而不需要最终值。如果迭代次数为f(z)时z=0的轨道,则点c属于M=z^2+c保持有界。离开某个磁盘所需的迭代次数通常用于给M的补码上色。@MartinR-啊,我相信你是对的。但在这种情况下,将每个计算分派到全局线程可能不是一个好主意,因为他可能会耗尽所有可用的工作线程。最好将并发性提高一个级别,并使用Swift 3中的
dispatch\u apply
(aka
concurrentPerform
)来享受一定程度的并发性。我相应地更新了我的答案。非常好(但我只能向上投票一次:)–顺便说一句,
dispatch\u apply
有一个
queue
参数。concurrent对哪个队列执行
dispatch?在我看来,这些问题回答得并不清楚,但也许我忽略了什么。@MartinR-查找
concurrentPerform
并一直追溯到调用
dispatch\u apply
,它使用“当前线程的根队列…”(即全局并发队列之一或使用
dispatch\u root\u queue\u create()创建的队列)。如果没有这样的队列,将使用默认优先级的全局并发队列。“非常感谢您的详细建议。我将继续使用,并将返回我的结果。