Pour envoyer de manière asynchrone, appelez async
dans la file d'attente appropriée. Par exemple, vous pouvez modifier cette méthode pour effectuer le calcul dans une file d'attente globale, puis renvoyer le résultat dans la file d'attente principale. Soit dit en passant, quand vous faites cela, vous passez de retourner le résultat immédiatement à l'aide d'une fermeture de gestionnaire d'achèvement qui la méthode asynchrone fera appel lorsque le calcul est effectué:
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)
}
}
}
Et vous appeler comme ceci:
// 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 a émis l'hypothèse que vous calculez un ensemble de Mandelbrot. Si c'est le cas, envoyer le calcul de chaque point dans une file d'attente globale n'est pas une bonne idée (car ces files d'attente globales envoient leurs blocs aux threads de travail, mais ces threads de travail sont assez limités). Si vous souhaitez éviter d'utiliser tous ces threads de travail de file d'attente globale, un choix simple consiste à supprimer l'appel async
de votre routine qui calcule un point individuel et à distribuer la routine entière qui itère dans tous les threads. valeurs complexes à un fil de fond:
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)))
}
}
C'est résout les « obtenir sur le fil conducteur » et « ne pas utiliser les threads de travail » des problèmes, mais maintenant nous avons balancé d'utiliser trop de travailleurs threads, à utiliser uniquement un thread de travail, n'utilisant pas pleinement l'appareil. Nous voulons vraiment faire autant de calculs en parallèle (sans épuiser les threads de travail). Une approche, lorsque vous effectuez une boucle for
pour des calculs complexes, consiste à utiliser dispatch_apply
(maintenant appelé concurrentPerform
dans Swift 3). Cela ressemble à une boucle for
, mais elle exécute chacune des boucles simultanément les unes par rapport aux autres (mais, à la fin, attend que toutes ces boucles simultanées soient terminées). Pour ce faire, remplacez la boucle for
extérieure avec 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)))
}
}
Le concurrentPerform
(anciennement connu sous le nom dispatch_apply
) effectuera les différentes itérations de cette boucle en même temps, mais il permettra d'optimiser automatiquement le nombre de threads simultanés pour les capacités de votre appareil. Sur mon MacBook Pro, cela a rendu le calcul 4,8 fois plus rapide que la simple boucle for
. Remarque, je envoie toujours le tout dans une file d'attente globale (car concurrentPerform
s'exécute de manière synchrone, et nous ne voulons jamais effectuer de calculs lents et synchrones sur le thread principal), mais concurrentPerform
exécutera les calculs en parallèle. C'est une excellente façon de profiter de la simultanéité dans une boucle for
de manière à ne pas épuiser les threads de travail GCD.
Par ailleurs, vous avez dit que vous mettez à jour NSProgressIndicator
. Idéalement, vous voulez le mettre à jour au fur et à mesure que chaque pixel est traité, mais si vous le faites, l'interface utilisateur risque d'être retardée, incapable de suivre toutes ces mises à jour. Vous finirez par ralentir le résultat final pour permettre à l'interface utilisateur de rattraper toutes ces mises à jour des indicateurs de progression.
La solution consiste à découpler la mise à jour de l'interface utilisateur des mises à jour de progression. Vous voulez que les calculs d'arrière-plan vous indiquent que chaque pixel est mis à jour, mais que vous voulez que l'indicateur de progression soit mis à jour, en disant à chaque fois "ok, mettez à jour la progression avec le nombre de pixels calculé depuis la dernière vérification". Il y a des techniques manuelles lourdes pour le faire, mais GCD fournit une solution vraiment élégante, une source d'expédition, ou plus précisément, un DispatchSourceUserDataAdd
.
donc définir les propriétés de la source d'expédition et un compteur de garder une trace de combien de pixels ont été traités jusqu'ici:
puis mis en place un gestionnaire d'événements pour la source d'envoi, qui met à jour les progrès indicateur:
source.setEventHandler() { [unowned self] in
self.pixelsProcessed += self.source.data
self.progressIndicator.doubleValue = Double(self.pixelsProcessed)/Double(width * height)
}
source.resume()
Et puis, comme vous traitez les pixels, vous pouvez simplement add
à votre source du fil de fond:
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)
}
}
Si vous faites cela, il mettra à jour l'interface utilisateur avec la plus grande fréquence possible, mais il ne sera jamais retardé avec une file d'attente de mises à jour. La source d'expédition fusionnera ces appels add
pour vous.
Vous devez exécuter le calcul long en arrière-plan. – rmaddy
Complètement sans rapport, mais je pourrais suggérer de déplacer certains des calculs 'Complex' dans votre' struct'/'class', et vous finiriez avec un algorithme de Mandelbrot plus intuitif: https://gist.github.com/robertmryan/ 91536bf75e46cbdaed92c37e99fdbe7d – Rob