Swift Language
Concurrence
Recherche…
Syntaxe
Swift 3.0
DispatchQueue.main // Récupère la file d'attente principale
DispatchQueue (label: "my-serial-queue", attributs: [.serial, .qosBackground]) // Crée votre propre file d'attente série privée
DispatchQueue.global (attributs: [.qosDefault]) // Accéder à l'une des files d'attente simultanées globales
DispatchQueue.main.async {...} // Envoie une tâche de manière asynchrone au thread principal
DispatchQueue.main.sync {...} // Envoie une tâche de manière synchrone au thread principal
DispatchQueue.main.asyncAfter (date limite: .now () + 3) {...} // Envoie une tâche de manière asynchrone au thread principal à exécuter après x secondes
Swift <3.0
dispatch_get_main_queue () // Récupère la file d'attente principale sur le thread principal
dispatch_get_global_queue (dispatch_queue_priority_t, 0) // Récupère la file d'attente globale avec la priorité spécifiée dispatch_queue_priority_t
dispatch_async (dispatch_queue_t) {() -> Void in ...} // Envoie une tâche de manière asynchrone sur le dispatch_queue_t spécifié
dispatch_sync (dispatch_queue_t) {() -> Void in ...} // Envoie une tâche de manière synchrone sur le dispatch_queue_t spécifié
dispatch_after (dispatch_time (DISPATCH_TIME_NOW, Int64 (nanosecondes)), dispatch_queue_t, {...}); // Envoie une tâche sur le dispatch_queue_t spécifié après des nanosecondes
Obtention d'une file d'attente Grand Central Dispatch (GCD)
Grand Central Dispatch travaille sur le concept de "files d'attente d'expédition". Une file d'attente de distribution exécute les tâches que vous avez désignées dans l'ordre dans lequel elles ont été passées. Il existe trois types de files d'attente:
- Serial Dispatch Queues (aka files d'attente de distribution privées) exécutent une tâche à la fois, dans l'ordre. Ils sont fréquemment utilisés pour synchroniser l'accès à une ressource.
- Les files d'attente de répartition simultanées ( c.-à-d . Les files d'attente de répartition globales) exécutent une ou plusieurs tâches simultanément.
- La file d'attente de répartition principale exécute les tâches sur le thread principal.
Pour accéder à la file d'attente principale:
let mainQueue = DispatchQueue.main
let mainQueue = dispatch_get_main_queue()
Le système fournit des files d'attente de répartition mondiales simultanées (globales à votre application), avec des priorités variables. Vous pouvez accéder à ces files d'attente en utilisant la classe DispatchQueue
dans Swift 3:
let globalConcurrentQueue = DispatchQueue.global(qos: .default)
équivalent à
let globalConcurrentQueue = DispatchQueue.global()
let globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
Dans iOS 8 ou version ultérieure, les valeurs de qualité de service pouvant être transmises sont les .userInteractive
: .userInitiated
, .default
, .utility
, .background
et .background
. Ceux-ci remplacent les constantes DISPATCH_QUEUE_PRIORITY_
.
Vous pouvez également créer vos propres files d'attente avec différentes priorités:
let myConcurrentQueue = DispatchQueue(label: "my-concurrent-queue", qos: .userInitiated, attributes: [.concurrent], autoreleaseFrequency: .workItem, target: nil)
let mySerialQueue = DispatchQueue(label: "my-serial-queue", qos: .background, attributes: [], autoreleaseFrequency: .workItem, target: nil)
let myConcurrentQueue = dispatch_queue_create("my-concurrent-queue", DISPATCH_QUEUE_CONCURRENT)
let mySerialQueue = dispatch_queue_create("my-serial-queue", DISPATCH_QUEUE_SERIAL)
Dans Swift 3, les files d'attente créées avec cet initialiseur sont en série par défaut et le passage de .workItem
à la fréquence d'autorelease garantit qu'un pool d'autorelease est créé et vidé pour chaque élément de travail. Il y a aussi .never
, ce qui signifie que vous allez gérer vous-même vos pools d'autorelease, ou .inherit
qui hérite des paramètres de l'environnement. Dans la plupart des cas, vous n'utiliserez probablement jamais .never
sauf en cas de personnalisation extrême.
Exécution de tâches dans une file d'attente Grand Central Dispatch (GCD)
Pour exécuter des tâches sur une file d'attente de distribution, utilisez les méthodes sync
, async
et after
.
Pour envoyer une tâche dans une file d'attente de manière asynchrone:
let queue = DispatchQueue(label: "myQueueName")
queue.async {
//do something
DispatchQueue.main.async {
//this will be called in main thread
//any UI updates should be placed here
}
}
// ... code here will execute immediately, before the task finished
Pour envoyer une tâche dans une file d'attente de manière synchrone:
queue.sync {
// Do some task
}
// ... code here will not execute until the task is finished
Pour envoyer une tâche dans une file d'attente après un certain nombre de secondes:
queue.asyncAfter(deadline: .now() + 3) {
//this will be executed in a background-thread after 3 seconds
}
// ... code here will execute immediately, before the task finished
REMARQUE: Toute mise à jour de l'interface utilisateur doit être appelée sur le thread principal! Assurez-vous de mettre le code des mises à jour d'interface utilisateur dans
DispatchQueue.main.async { ... }
Types de file d'attente:
let mainQueue = dispatch_get_main_queue()
let highQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
let backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)
Pour envoyer une tâche dans une file d'attente de manière asynchrone:
dispatch_async(queue) {
// Your code run run asynchronously. Code is queued and executed
// at some point in the future.
}
// Code after the async block will execute immediately
Pour envoyer une tâche dans une file d'attente de manière synchrone:
dispatch_sync(queue) {
// Your sync code
}
// Code after the sync block will wait until the sync task finished
Pour envoyer une tâche après un intervalle de temps (utilisez NSEC_PER_SEC
pour convertir les secondes en nanosecondes):
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
// Code to be performed in 2.5 seconds here
}
Pour exécuter une tâche de manière asynchrone et mettre à jour l'interface utilisateur:
dispatch_async(queue) {
// Your time consuming code here
dispatch_async(dispatch_get_main_queue()) {
// Update the UI code
}
}
REMARQUE: Toute mise à jour de l'interface utilisateur doit être appelée sur le thread principal! Assurez-vous de mettre le code des mises à jour de l'interface utilisateur dans
dispatch_async(dispatch_get_main_queue()) { ... }
Boucles simultanées
GCD fournit un mécanisme pour effectuer une boucle, les boucles se produisant simultanément les unes par rapport aux autres. Ceci est très utile lorsque vous effectuez une série de calculs coûteux.
Considérez cette boucle:
for index in 0 ..< iterations {
// Do something computationally expensive here
}
Vous pouvez effectuer ces calculs simultanément à l'aide de concurrentPerform
(dans Swift 3) ou de dispatch_apply
(dans Swift 2):
DispatchQueue.concurrentPerform(iterations: iterations) { index in
// Do something computationally expensive here
}
dispatch_apply(iterations, queue) { index in
// Do something computationally expensive here
}
La fermeture de la boucle sera appelée pour chaque index
entre 0
et, mais sans inclure, les iterations
. Ces itérations seront exécutées simultanément les unes par rapport aux autres et, par conséquent, l'ordre qu'elles exécutent n'est pas garanti. Le nombre réel d'itérations qui se produisent simultanément à un moment donné est généralement dicté par les capacités du périphérique en question (par exemple, le nombre de cœurs du périphérique).
Quelques considérations spéciales:
concurrentPerform
/dispatch_apply
peut exécuter les boucles simultanément les unes par rapport aux autres, mais tout cela se produit de manière synchrone par rapport au thread à partir duquel vous l'appelez. Donc, n'appelez pas cela depuis le thread principal, car cela bloquera ce thread jusqu'à ce que la boucle soit terminée.Étant donné que ces boucles se produisent simultanément les unes par rapport aux autres, vous êtes responsable de la sécurité des threads. Par exemple, si vous mettez à jour un dictionnaire avec les résultats de ces calculs coûteux, veillez à synchroniser ces mises à jour vous-même.
Notez qu'il existe des frais généraux associés à l'exécution de boucles concurrentes. Par conséquent, si les calculs effectués à l'intérieur de la boucle ne nécessitent pas beaucoup de calculs, vous pouvez constater que les performances obtenues en utilisant des boucles simultanées peuvent être diminuées, voire totalement compensées, par la surcharge associée à la synchronisation de tous ces threads.
Vous êtes donc responsable de déterminer la quantité correcte de travail à effectuer dans chaque itération de la boucle. Si les calculs sont trop simples, vous pouvez utiliser "striding" pour inclure plus de travail par boucle. Par exemple, plutôt que de faire une boucle simultanée avec 1 million de calculs triviaux, vous pouvez effectuer 100 itérations dans votre boucle, en effectuant 10 000 calculs par boucle. De cette façon, il y a suffisamment de travail effectué sur chaque thread, de sorte que la surcharge associée à la gestion de ces boucles simultanées devient moins importante.
Exécution de tâches dans une OperationQueue
Vous pouvez penser à OperationQueue
comme une ligne de tâches en attente d'exécution. Contrairement aux files d'attente d'expédition dans GCD, les files d'attente d'opérations ne sont pas des FIFO (premier entré, premier sorti). Au lieu de cela, ils exécutent des tâches dès qu'ils sont prêts à être exécutés, tant qu'il y a suffisamment de ressources système pour le permettre.
Obtenez le principal OperationQueue
:
let mainQueue = OperationQueue.main
Créez une OperationQueue
personnalisée:
let queue = OperationQueue()
queue.name = "My Queue"
queue.qualityOfService = .default
Qualité de service spécifie l’importance du travail ou la probabilité que l’utilisateur compte sur les résultats immédiats de la tâche.
Ajouter une Operation
à une OperationQueue
:
// An instance of some Operation subclass
let operation = BlockOperation {
// perform task here
}
queue.addOperation(operation)
Ajouter un bloc à OperationQueue
:
myQueue.addOperation {
// some task
}
Ajoutez plusieurs Operation
à une OperationQueue
:
let operations = [Operation]()
// Fill array with Operations
myQueue.addOperation(operations)
Ajustez le nombre d' Operation
pouvant être exécutées simultanément dans la file d'attente:
myQueue.maxConcurrentOperationCount = 3 // 3 operations may execute at once
// Sets number of concurrent operations based on current system conditions
myQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount
La suspension d'une file d'attente l'empêchera de lancer l'exécution d'opérations existantes non démarrées ou de nouvelles opérations ajoutées à la file d'attente. La manière de reprendre cette file d'attente consiste à définir isSuspended
sur false
:
myQueue.isSuspended = true
// Re-enable execution
myQueue.isSuspended = false
La suspension d'une OperationQueue
n'arrête pas ou n'annule pas les opérations en cours d'exécution. On ne doit tenter de suspendre une file d'attente que vous avez créée, pas les files d'attente globales ou la file d'attente principale.
Création d'opérations de haut niveau
Le framework Foundation fournit le type Operation
, qui représente un objet de haut niveau qui encapsule une partie du travail pouvant être exécutée dans une file d'attente. Non seulement la file d'attente coordonne les performances de ces opérations, mais vous pouvez également établir des dépendances entre les opérations, créer des opérations annulables, limiter le degré de concurrence utilisé par la file d'attente d'opérations, etc.
Operation
deviennent prêtes à être exécutées lorsque toutes ses dépendances sont terminées. La propriété isReady
alors true
.
Créez une sous-classe Operation
simple non concurrente:
class MyOperation: Operation {
init(<parameters>) {
// Do any setup work here
}
override func main() {
// Perform the task
}
}
class MyOperation: NSOperation {
init(<parameters>) {
// Do any setup work here
}
override func main() {
// Perform the task
}
}
Ajouter une opération à une OperationQueue
:
myQueue.addOperation(operation)
Cela exécutera l'opération simultanément dans la file d'attente.
Gérer les dépendances sur une Operation
.
Les dépendances définissent d'autres Operation
qui doivent s'exécuter dans une file d'attente avant que l' Operation
soit considérée prête à être exécutée.
operation2.addDependency(operation1)
operation2.removeDependency(operation1)
Exécutez une Operation
sans file d'attente:
operation.start()
Les dépendances seront ignorées. S'il s'agit d'une opération simultanée, la tâche peut toujours être exécutée simultanément si sa méthode de start
décharge le travail sur les files d'attente en arrière-plan.
Opérations simultanées
Si la tâche que l' Operation
consiste à effectuer est, lui - même, asynchrone, (par exemple , une URLSession
tâche de données), vous devez mettre en œuvre l' Operation
comme une opération concurrente. Dans ce cas, votre implémentation isAsynchronous
doit retourner true
, vous aurez généralement une méthode start
qui effectue une configuration, puis appelle sa méthode main
qui exécute réellement la tâche.
Lorsque vous implémentez une Operation
asynchrone, vous devez implémenter les méthodes isExecuting
, isFinished
et KVO. Ainsi, lorsque l'exécution commence, la propriété isExecuting
devient true
. Lorsqu'une Operation
termine sa tâche, isExecuting
est défini sur false
et isFinished
est défini sur true
. Si l'opération est annulée, isCancelled
et isFinished
true
. Toutes ces propriétés sont observables par valeur-clé.
Annuler une Operation
.
L'appel d' cancel
simplement la propriété isCancelled
sur true
. Pour répondre à l'annulation depuis votre propre sous-classe Operation
, vous devez vérifier la valeur de isCancelled
au moins périodiquement dans main
et répondre de manière appropriée.
operation.cancel()