La programmation concurrente avec Grand Central Dispatch

Aujourd’hui nous allons parler de la programmation concurrente sous iOS (en Swift bien évidemment) avec le framework natif Grand Central Dispatch (GCD). Si vous n’êtes pas familier avec la programmation concurrente je vous suggère de commencer par faire un tour du coté de wikipedia. Nous allons voir quelle philosophie est caché derrière et comment l’utiliser à bon escient.

Grand Central Dispatch

GCD (Grand Central Dispatch) est une framework fournissant des APIs de bas niveau pour traiter la programmation concurrente au seins des plateformes iOS et OSX. Tous les frameworks de plus haut niveaux proposant de gérer la concurrence chez Apple, comme les NSOperation, sont basés sur GCD. Ses APIs permettent de diviser le travail d’un processus en différentes tâches individuelles, puis de nous aider à orchestrer l’exécution de ces tâches en parallèle ou en séquentielle à l’aide de file. Il permet de nous abstraire de la notion de thread, processeur, coeur, etc. et il optimise la gestion des resources par la même occasion avec son propre threadpool.

Les Queues

Une notion fondamentale sur laquelle repose GCD est celle de file FIFO (First In, First Out). Les tâches que l’on veut executer vont d’abord être insérées dans ces files (queues) puis GCD va venir les récupérer individuellement pour les « dispatcher » aux bons threads/processeurs/coeurs. On peut distinguer 2 typologies de file :

  • File concurrente (concurrent) : exécute plusieurs tâches en parallèle
  • File séquentielle (serial) : exécute les tâches une à une en série

Voici les 3 catégories de file existantes :

  • Main queue : équivalent au thread principal qui gère principalement l’UI de l’application.
  • Global queue : le système nous fournit 3 globals queues avec des priorités différentes (HIGH, DEFAULT et LOW). Ces 3 files sont de type concurrente.
  • Custom queue : des files d’attentes que l’on peut créer nous même et du type que l’on veut (séquentielle ou concurrente).

En pratique

Commençons par créer une des 3 catégories de file:

// Main queue
let mainQueue = dispatch_get_main_queue()

// Global queue: HIGH, DEFAULT ou LOW
let globalDefaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

// Custom queue: SERIAL ou CONCURRENT
let customSerialQueue = dispatch_queue_create("fr.swift-tuto.queue", DISPATCH_QUEUE_SERIAL)

Revenons un peu sur la récupération d’une des global queue ou de la création d’une custom queue qui demande un peu plus d’explication que juste une ligne de code. Pour récupérer une global queue on utilise la méthode dispatch_get_global_queue avec comme premier paramètre un des 3 niveaux de priorité et le second paramètre est toujours 0 car non utilisé pour le moment. Pour la création d’une custom queue, on définit d’abord un identifiant (il est recommandé d’utiliser le format reverse-DNS) puis la typologie de queue.

Maintenant que nous savons comment créer des files, comment les utilisons nous me direz vous ? C’est très simple, grâce au « dispatcher » ! Voici un petit example :

let lowQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)

print("before")

dispatch_async(lowQueue) { 
  print("inside first block")
  let html = getRemoteHTMLAt("http://swift-tuto.fr")

  dispatch_async(dispatch_get_main_queue()) {
    print("inside second block")

    // Toujours utiliser les composants UIKit dans le thread principal 
    myWebView.loadHTMLString(html, baseURL: nil)

    print("finished!")
  }
}

print("after")

// La console va afficher ceci dans cet ordre :
// before
// after
// inside first block
// inside second block
// finished!

Essayons de décomposer tous ça. On récupère pour commencer une file de priorité basse. Ensuite nous « dispatchons » un block de code de manière asynchrone sur la file de basse priorité récupéré auparavant. Ce block de code var récupérer de manière synchrone une chaîne HTML pour la stocker dans la variable html. Pour finir nous mettons à jour notre myWebView dans le thread principal avec le HTML récupérer. Il est important d’effectuer la dernière étape dans le thread principal car UIKit n’est pas threadsafe et si vous mettez à jour vos composants dans un autre thread votre application risque de planter.

Pour aller plus loin…

Avec GCD vous pouvez gérer tous ce qui concerne la programmation concurrente. Nous allons voir dans cette sections différent cas d’utilisation un peu plus poussé.

Par example vous avez plusieurs tâches à exécuter et vous souhaitez attendre que l’ensemble de ces tâches soient effectué avant de faire autre chose. Pour cela il faut utiliser les dispatch_group_t :

let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
let group = dispatch_group_create()

for _ in 0 ..< 10 {
  dispatch_group_enter(group)

  dispatch_async(queue) {
    // Long job process

    dispatch_group_leave(group)
  }
}

dispatch_group_notify(group, queue) { 
  print("All jobs done!")
}

Afin d'attendre que l'ensemble des tâches soient effectuées on créer un group et dans notre boucle nous définissons quand une tâche commence dans le groupe et surtout quand la tâche est fini. Il faut imaginer cela comme un compteur, à chaque dispatch_group_enter ce compteur est incrémenté de 1, et à chaque dispatch_group_leave il est décrémenté de 1. A la fin nous définissons un dispatch_group_notify qui écoute le compteur du group et qui exécute le block dans la queue donnée quand il arrive à 0.

Un deuxième cas très utile peut être le fait de vouloir exécuter du code après un certain temps. Si vous avez ce genre de cas pensez tout de suite à GCD et sa méthode dispatch_after:

let twoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(2 * NSEC_PER_SEC))
let queue      = dispatch_get_main_queue()

print("before")
dispatch_after(twoSeconds, queue) {
  print("2 seconds after")
}
print("after")

// Affiche
// before
// after
// 2 seconds after

Ici la partie la plus compliqué est la définition du temps après lequel vous souhaitez exécuté votre block de code avec un dispatch_time_t.

Conclusion

GCD est un framework très puissant qui permet de réellement s'abstraire des contraintes de bas niveaux inhérente à la programmation concurrente. Cependant il demande un peu de travail avant de pouvoir en saisir toute sa puissance et éviter les erreurs de débutant. Si vous avez des questions, n'hésitez pas à nous laisser des commentaires.

1 Etoile2 Etoiles3 Etoiles4 Etoiles5 Etoiles (4 votes, average: 4,50 out of 5)
Loading...

Aucun commentaire

Time limit is exhausted. Please reload CAPTCHA.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.