Accueil Nos publications Blog [Session 3T] Parallélisme et asynchronisme en .NET

[Session 3T] Parallélisme et asynchronisme en .NET

VS2010Nathanaël Marchand a présenté une session 3T jeudi 24 janvier avec comme sujet “le parallélisme et asynchronisme en .NET”.

Voici ce qui nous a été présenté.

Que sont l’asynchronisme et le parallélisme ?

La première partie de la présentation a été de redéfinir assez clairement ce que sont l’asynchronisme et le parallélisme.

L’asynchronisme permet de commencer une seconde tâche sans attendre la fin de la première et sans changer le flux d’exécution du programme. Différents threads seront utilisés ce qui permet de profiter des différents cœurs de nos processeurs.

Le parallélisme, quant à lui, permet de résoudre une tâche de manière simultanée en éclatant le jeu de données. Chaque partie du jeu de données sera résolue par une tâche dans un thread séparé, en parallèle d’autres parties. C’est un moyen de maximiser la performance en utilisant également tous les cœurs de nos processeurs

Attention toutefois, l’asynchronisme n’implique pas le parallélisme et réciproquement.

Après avoir vu les définitions de ces notions, Nathanaël nous a fait une rétrospective des outils fournis par le Framework .NET pour utiliser les deux notions.

Quels outils nous ont été fournis par le Framework ?

Asynchronisme

Deux patterns d’asynchronisme ont été apportés par le framework 3.5 :

Parallélisme

La classe de base se chargeant d’organiser la parallélisation d’une tâche est la classe Thread. Etant donné qu’il n’est pas simple de l’utiliser, nous préférons passer par la classe ThreadPool.

Pour faciliter l’utilisation de l’asynchronisme, le Framework v4.5 amène une couche de sucre syntaxique : les fameux async et await. Cette couche est basée sur l’utilisation de Task dont nous allons parler maintenant en passant au Parallel Framework.

Parallel Framework

Ce fut la grosse partie de la soirée. Nathanaël y a parlé de PLINQ, de la classe Parallel et de la TPL (Task Parallel Library).

Vue haut niveau de l’architecture de Parallel Programming de framework .net 4[/caption]

Cette notion de parallélisme se base sur trois points :

  1. Éclater les données en petites parties
  2. Traiter ces parties en utilisant le multithreading
  3. Rassembler les résultats au fur et à mesure de leur résolution, de manière thread safe

Deux philosophies sont utilisables à ce moment : le parallélisme de données et celui de tâches.

Le parallélisme de données

Ce type de parallélisme est basé sur un éclatement du jeu de données à traiter. Il est structuré, c’est-à-dire que le parallélisme commence et finit au même endroit dans le code. Il est hautement parallélisable et scalable. Enfin, il est déclaratif.

Ce type de parallélisation est utilisable au travers de PLINQ et de la classe Parallel.

PLINQ (parallel Linq) sert à faire du linq en parallèle. Toutefois vous devez faire attention car toutes les méthodes utilisables dans linq ne sont pas parallélisables (Join, groupBy, …). Plinq est également assez configurable étant donné qu’il met à disposition du développeur son niveau de parallélisation, la manière de partitionner les données, l’ordre dans lequel traiter les chunks et permet même d’annuler d’un process.

La seconde manière d’utiliser du parallélisme de données est de passer par la classe Parallel (sur laquelle se base Plinq). Différents verbes sont à votre disposition comme Parallel.Invoke pour les exécutions simultanées, ou encore le For et le Foreach pour les boucles.

Passons maintenant à la seconde philosophie de parallélisme, celui de tâche.

Le parallélisme de tâches

Ce parallélisme est basé sur la classe Task dont nous avons brièvement parlé avant. Il permet plein de choses comme le chaînage d’actions, la gestion d’erreurs, ou l’encapsulation de patterns asynchrones.

Il faut savoir qu’une tâche est awaitable, on peut écrire :



await FunctionThatReturnsATask ();

Une chose importante est la synchronisation des tâches, c’est-à-dire de l’attente du résultat. Plusieurs moyens sont utilisables comme :

  • Task.WaitAll
  • Task.WaitAny

Et dans le cas où on veut continuer le traitement après cette synchronisation, on peut plutôt utiliser des verbes de chaînage comme :

  • Task.Factory.ContinueWhenAll
  • Task.Factory.ContinueWhenAny
  • Task.ContinueWith

Il est également possible d’utiliser des patterns asynchrones, des tokens d’annulation ou d’ordonnancer les tâches.

Un autre point important : le problème de la mise à jour de l’UI. En effet, seul le thread de l’UI a le droit de la mettre à jour. Pour cela il suffit d’utiliser le TaskScheduler. Notamment, lors du chaînage  il est possible de fournir un contexte qui va définir dans quel thread va s’exécuter la partie de code. Le contexte de l’UI est simplement récupérable en utilisant :


 // Get the UI scheduler for the thread that created the form:
_uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

Pour finir sur les tâches je dirais que c’est vraiment un outil puissant, et comme tout outil, il faut vraiment comprendre comment BIEN les utiliser. Il ne suffit pas d’en mettre partout pour avoir une super application. Néanmoins, une fois bien utilisé, cet outil se révèle vraiment puissant.

Petite boite à outils

Nous  avons fini cette soirée sur la constitution d’une boite à outil, ce qui a permis de nous présenter des petits éléments qui peuvent grandement nous aider.

Deux éléments m’ont plu :

Conclusion

Pour conclure, je dirais que cela aura été une soirée très instructive. En tout cas pour ma part, j’y ai appris pas mal de choses au travers de la présentation de ces différents outils. Mais je redirais ce que j’ai dit plus haut, il ne faut pas perdre de vue que l’asynchronisme et le parallélisme sont des outils. Ils peuvent être très utiles mais peuvent aussi être contre-productif. En effet, ils permettent de maximiser les performances de vos applications. Néanmoins, Nathanaël a essayé de nous faire passer le message, maximiser les performances ne veut pas dire faire mieux que ce que vous avez à l’heure actuelle ! Il se peut très bien qu’un code non parallélisé soit plus performant que son pendant en parallèle.

Pour plus d’informations

Pour plus d’informations, je vous conseille d’aller lire le sujet de Joe Albahari sur le Threading en c# : https://www.albahari.com/threading/. Il en décrit les mécanismes en 5 parties. Une véritable bible sur le threading.

Le second lien sera celui du blog de la team s’occupant de la programmation parallèle : https://blogs.msdn.com/b/pfxteam/

Le dernier n’est pas un lien mais un endroit où trouver de nombreux exemples : les samples Linqpad. Si vous ne connaissez pas l’outil je vous le recommande fortement. Vous pouvez trouver l’outil ici : https://www.linqpad.net/