La programmation parallèle en C#
Mis à jour le 11/11/2024
Cédric BRASSEUR
- Ancien étudiant de l'Exia.cesi
- 4 ans consultant chez Amaris, placé chez Euro Information Dev
- Auto-entrepreneur depuis début 2020 (Création de sites web, applications mobiles & formateur)
Et vous ?
- Nom, prénom
- Etudes réalisées
- Connaissances en dév
- Autres infos appréciées

Plan du cours
- Introduction à la théorie de la programmation parallèle
- Explications des classes de PP in C#
- Task
- Parallel
- PLinq
- ...
- Démonstration et exemples
- Exercices de PP in C#
- Exercices simples d'utilisation
- Exercices en PLinq
- Exercice de mise en place d'une API
- Exercice complet de transformation d'un code synchrone en code asynchrone

// in C#
Objectifs de la formation
- Appréhender la notion de programmation parallèle
- Concepts de la prog. parallèle
- Exemple et démonstration
- Exercices applicatifs pour s'approprier la notion








Théorie et programmation parallèle
Utilité de la PP
La programmation parallèle a pour objectif de faire tourner du code sur plusieurs processeurs (ou plusieurs cœurs). Ceci permettant de réaliser des opérations en parallèle et donc dans certains cas optimiser le temps pour de lourds traitements.

Processeurs
Utilité de la PP
L'objectif est donc de diviser un problème en plusieurs parties et faire s'exécuter des bouts de solutions de manière parallèle afin d'optimiser le temps de traitement.
A noter que c'est utilisable dans certains contexte mais que dans d'autres, ça peut être s'ajouter de la complexité pour peu de gains.
Quand utiliser la Programmation Parallèle ?
Il y a plusieurs cas d'usages récurrents pour la PP :
- Lorsque l'on souhaite mettre en place une interface (UI) et que nous souhaitons laisser l'affichage global s'effectuer et réaliser les opérations annexes en background
- Lors de la mise en place d'une API : Double utilité, d'abord pour traiter les demandes de clients différents & pour pouvoir effectuer plusieurs requêtes en simultanées
- Lors d'opérations couteuses que l'on peut diviser en plusieurs petites parties indépendantes
Types de PP
Task-bases Asynchronous Pattern (TAP)
Event-bases Asynchronous Pattern (EAP)
Asynchronous Programming Model (APM)
On utilise une seule méthode pour compléter une suite de tâche asynchrone. C'est le type recommandé en .Net et nous nous attarderons sur ce type.
On utilise des events pour produire des comportements asynchrone. C'est en quelques sortes l'ancienne méthode donc nous l'aborderons pas.
Mise en place de méthodes avec Begin & End et une interface IAsyncResult pour traiter des opérations asynchrones. C'est une façon de faire qui n'est plus recommandé du tout aujourd'hui.
.Net 1.1
.Net 2.0
.Net 4.5
Threads VS Process
Process | Threads |
---|---|
Opérations lourdes | Opérations plus légères |
A son propre espace mémoire | Partage la mémoire du process auquel ils appartiennent |
Communication inter-process lente et adresse mémoire différentes | D'où la communication plus rapide entre threads |
Passer d'un process à l'autre est coûteux | Passer d'un thread à l'autre est moins coûteux |
Aucun partage de mémoire | Partage de la mémoire |
Un process est une unité de code qui permet de réaliser des opérations (instructions). On peut voir les Threads comme des micro-process qui peuvent communiquer entre eux et utiliser une mémoire commune
Threads en détails
Juste à titre informatif, voici comment fonctionnent les Threads
- Register : C'est l'unité de travail qui contient les données nécessaire au process (adresse de stockage,...)
- Program Counter : Le pointeur d'instruction, il joue un rôle organisationnel et garde une trace de qui travaille sur quoi.
- Stack : Une structure de donnée qui stock les données d'un programme. C'est à distinguer de la mémoire dynamique, on l'appelle "Heap"
- Thread Scheduler : C'est l'OS qui défini quel process s'exécutera sur quel processeur

Synchrone VS Asynchrone
Ces termes doivent déjà vous parler mais il s'agit de programmation concurrentielle & de programmation parallèle.
Concurrentielle
Parallèle
CPU
Unité de travail
CPU 1 / Thread
Unité de travail
CPU 2 / Thread
Unité de travail
Parallèle
Unité de travail
Unité de travail
Asynchrone
=
Parallèle + interdépendances
Synchrone exemple
Quelques informations sur un programme qui utilise les process : Google Chrome.
Comme on peut le voir dans le Task Manager, Chrome utilise plusieurs processus, d'ailleurs il en met en place parfois plusieurs pour chaque onglet. Ceci apportant plusieurs avantages :
- Protège des bugs qui peuvent impacter les autres onglets.
- Isole le code (Javascript par exemple) permet d'éviter d'utiliser trop de CPU et de mémoire en une seule fois

Asynchrone exemple
Voici un exemple que j'utiliserai afin de vous montrer comment faire de la programmation parallèle (Exemple de Microsoft)
Concurrentielle
Asynchrone


Aller plus loin dans la théorie
Bien que ça ne soit pas vraiment au programme de ce cours, je sais que ça en intéressera plus d'un (une) d'approfondir la théorie de la PP.
Quelques termes pour guider vos recherches
- Loi de Moore
- Cores (et puces avec plusieurs cœurs)
- SIMD (single instruction multiple data)
- Thread (méthode) / Process (module)
- Green thread (Fiber) / Kernel thread
- ILSpy (outil d'analyse)
- Coroutine
- Sémaphore
- Mutex
- Data Race
- Deadlocks
...
Des gens intéressés pour faire une petite présentation de la théorie ?
Les classes et méthodes en C# pour la programmation parallèle
Les classes de PP en C#
Nous allons voir plusieurs classes disponibles dans le Framework .Net afin de réaliser de la programmation parallèle en C# de nos jours (TAP) :
- Les mots clés async & await
- Task
- Parallel
- Action
- PLinq
Premièrement, le mot clé async permet de définir une méthode comme étant asynchrone.
Ensuite, le mot clé await permet de définir que l'on attend le retour d'une méthode asynchrone
Les mots clés await / aync
On peut avoir une méthode async en void, mais ceci nous forcerait à ne pas pouvoir attendre le retour, donc nous ne serions pas sûr de la fin de l'exécution de la méthode.
Si on déclare une méthode comme asynchrone, il doit y avoir un await dedans
Nous utilisons
également les méthodes asynchrones d'appels d'API ici


Les mots clés await / aync
Ces mots clés sont toujours utilisé en programmation parallèle, ils permettent de définir une méthode comme asychrone (async) et d'attendre le retour (await)
S'il on déclare une méthode comme asynchrone, il doit y avoir un await dedans


Nous utilisons
également les méthodes asynchrones d'appels d'API ici
On utilise Task en type de retour car il n'est pas possible d'attendre sur void
La classe Task
La classe Task a pour but de vous permettre de gérer vos appels asynchrones en C#, c'est la plus utilisée.
Voici les différentes possibilités que l'on va explorer :
Class | Objectif |
---|---|
Task | Gérer une unité de travail |
Task<TResult> | Unité de travail avec une valeur de retour |
TaskFactory | Créer une Task |
TaskFactory<TResult> | Créer une Task & continuité de Task avec le même type de retour |
TaskCompletionSource | Pour gérer manuellement le workflow d'une Task |
Premier exemple simple d'utilisation d'une Task :
On déclare le type de retour en Task afin de pouvoir l'await.

On utilise Task.WhenAll()
pour attendre que toutes les opérations soient
terminées
La classe Task
Task.WhenAll respecte l'ordre de passage des tasks dans le retour de result[] (même si les temps d'exécution sont différents)
Voici les méthodes que nous allons utiliser pour réaliser des opérations parallèles

On fait en sorte que
le thread attende 2 secondes pour simuler une tâche lourde
La classe Task
Attention, il y a plusieurs moyens d'appeler une Task, nous en verrons cinq. Dont trois seulement sont optimisés, ne faites pas l'erreur, sinon l'asynchronisme ne sert à rien.
On déclare le type de retour
en Task<string> afin de pouvoir l'await avec retour
Chaque tâche est attendue l'une après l'autre


Ce moyen attend les tâches l'une après l'autre et prend donc 6 secondes
La classe Task
Attention, il y a plusieurs moyens d'appeler une Task, nous en verrons cinq. Dont deux seulement sont optimisés, ne faites pas l'erreur, sinon l'asynchronisme ne sert à rien.
SendEmailAsync n'ayant
pas de type de retour, on peut l'appeler ainsi également
Ce moyen attend les tâches l'une après l'autre, sauf SendEmailAync et prend donc 4 secondes


Seul cet appel sans type de
retour sera réalisé en même temps que les autres
La classe Task
Attention, il y a plusieurs moyens d'appeler une Task, nous en verrons cinq. Dont deux seulement sont optimisés, ne faites pas l'erreur, sinon l'asynchronisme ne sert à rien.
On créer une Task
pour chaque méthode à appeler
Ce moyen réalise les tâches en parallèle, ceci prend donc 2 secondes

Les appels sont fait de manière asynchrone

La classe Task
Attention, il y a plusieurs moyens d'appeler une Task, nous en verrons cinq. Dont deux seulement sont optimisés, ne faites pas l'erreur, sinon l'asynchronisme ne sert à rien.
On créer une Task
pour chaque méthode à appeler
Ce moyen réalise les tâches en parallèle, ceci prend donc 2 secondes
On utilise Task.WhenAll
pour attendre les actions en parallèle


Clean !
La classe Task
Attention, il y a plusieurs moyens d'appeler une Task, nous en verrons cinq. Dont deux seulement sont optimisés, ne faites pas l'erreur, sinon l'asynchronisme ne sert à rien.
On créer une Task
pour chaque méthode à appeler
On créer une List de Tasks

On boucle sur la liste de tasks
Ce moyen prend également 2 secondes mais permet d'avoir la main sur les Tasks
On boucle sur la liste de tasks
On boucle sur la liste de tasks
Dès qu'une tâche est terminée, on la supprime avec Remove
La classe Task
Exercice class Task
Afin que vous pratiquiez un peu l'utilisation de la classe Task, vous devez réutiliser les exemples précédents et réaliser différents appels asynchrones pour pratiquer.
Le but est de reprendre le code et le comprendre :
- Le mot clé async / await avec deux requêtes vers
- https://jsonplaceholder.typicode.com/todos/1
- https://jsonplaceholder.typicode.com/todos/2
- Et afficher le contenu json dans un seul Console.WriteLine
- La classe Task
- Je vous envoie le code FakeAsyncTask et à vous de jouer
Posez des questions si des choses ne sont pas claires pour vous
Il est également possible d'utiliser une Factory pour créer une Task et la démarrer :

On peut également mettre
un appel à une méthode ici

Voici un exemple un peu plus complet qui permet de réaliser des opérations de manière parallèle ainsi que les ajouter à une somme totale et l'afficher.
Par contre impossible de faire des opérations asynchrones dans DoComputation
La classe Task
Il est également possible d'utiliser Run afin de réaliser des opérations similaires avec possibilité d'asynchronisme (recommandé)
Exemple avec Task.Run
La classe Task

Task permet également d'utiliser ContinueWith afin de pouvoir continuer un traitement asynchrone en s'assurant d'avoir l'ensemble des résultats attendus avant de continuer le code tout en gérant efficacement les exceptions.
Exemple ContinueWith
La classe Task

ContinueWith permet également de gérer les exceptions (il est d'ailleurs conseiller de gérer les différents statuts de retour). Voici un petit exemple
Exemple ContinueWith
La classe Task

Exercice class Task
Un petit exercice d'application du ContinueWith. Il vous est demandé de réalisé le code (via une Task<string>) permettant de gérer le retour de cette méthode async d'appel à une api quelconque.
Vous devez gérer les différents status (RanToCompletion & Faulted).
Pensez à tester avec une api qui n'existe pas (enlevez le i à official par exemple)
Posez des questions si des choses ne sont pas claires pour vous
static async Task<string> FetchDataAsync()
{
using (HttpClient httpClient = new HttpClient())
{
HttpResponseMessage response = await httpClient.GetAsync("https://official-joke-api.appspot.com/random_joke");
response.EnsureSuccessStatusCode(); // Permet de lever une exception si l'appel n'a pas renvoyé une réponse valable
return await response.Content.ReadAsStringAsync();
}
}
Autre exercice class Task
Le but de cet exercice est de travailler encore une fois la classe Task sur un exemple légèrement plus complexe et moins guidé.
Vous avez le code de la méthode "DoWork". Votre objectif est d'appeler trois fois cette méthode avec les paramètres suivants :
- "Task 1", 3000
- "Task2", 2000
- "Task3", 4000
Bien entendu vous devez appelez les trois méthodes de manière asynchrone.
Pour terminer, vous devez afficher le cumul des 3 délais retournés PUIS ajouter 500 au cumul pour l'afficher.
static int DoWork(string taskName, int delayMilliseconds)
{
Console.WriteLine("{0} starting...", taskName);
Task.Delay(delayMilliseconds).Wait();
Console.WriteLine("{0} completed.", taskName);
return delayMilliseconds;
}
La classe Parallel
Comme on l'a vu, la classe Task a pour objectif de réaliser une opération de manière asynchrone, souvent des opérations lourdes. Une autre possibilité pour la PP est d'utiliser la classe Parallel. Cette dernier va vous permettre d'effectuer un nombre de fois des opérations en parallèle. L'objectif est de simplifier la réalisation d'actions en parallèle.
A noter qu'on utilise Parallel lorsqu'il n'y a pas d'interdépendances entre les éléments que l'on veut rendre asynchrone.

Attention, ce moyen de paralléliser les instructions ne permet pas de garder l'ordre de départ de la liste.
Il est possible de limiter le nombre de processeurs utilisés lors de l'utilisation de la classe Parallel. Ceci se fait avec des options de la classe ParallelOptions.
Il existe aussi des options pour mettre en place un token d'annulation (CancellationToken) & pour choisir le planificateur de tâche (TaskScheduler). On l'a entrevu normalement, mais c'est aussi possible avec Task (et des constructeurs différents)

A noter, ceci est très rarement utile, surtout qu'il se peut que d'une machine à l'autre ces éléments varient.
La classe Parallel
Un autre exemple de code et une autre façon de pouvoir utiliser la classe Parallel. Notez dans l'exemple du dessous que nous utilisons une aggrégation de résultat.
Attention, ce moyen de paralléliser les instructions ne permet pas de garder l'ordre de départ de la liste.

La classe Parallel
Un autre exemple de code et une autre façon de pouvoir utiliser la classe Parallel
Attention, ce moyen de paralléliser les instructions ne permet pas de garder l'ordre des lignes provenant d'un fichier

La classe Parallel
Un autre exemple de code et une autre façon de pouvoir utiliser la classe Parallel, mais cette fois-ci avec Parallel.Invoke et en modifiant le retour (Upper / Lower)
Attention, ce moyen de paralléliser les instructions ne permet pas de garder l'ordre des lignes provenant d'un fichier

La classe Parallel
Dans ce contexte, nous ne travaillons donc plus avec des Task, mais avec des Action.
Chaque action peut être effectuée en parallèle et être appelé de manière asynchrone grâce à la Parallel.Invoke.
Ce code mettra à peine plus de 1,5 secondes à s'exécuter car les actions sont déclenchées en parallèle.
La classe Parallel

Exercice class Parralel
Une partie de ces exercices sont aussi présents dans les slides, mais ils ont été légèrement adapté pour un peu plus vous forcer à réfléchir durant la pratique.
Posez des questions si des choses ne sont pas claires pour vous
Envoyer Exercice1_PP.md
+ file1.txt
+ file2.txt
PLinq (Parallel with Linq)
Un dernier moyen existe afin de réaliser des opérations parallèles sur des listes d'éléments. Nous pouvons faire du PLinq pour réaliser du parallélisme avec Linq (mais c'est peu recommandé en règle général)
On utilise la méthode
AsParallel() sur une liste ou un tableau afin de rendre les itérations asynchrones
Ceci permet d'afficher les éléments de la liste de manière asynchrone, attention ici aussi ça ne garde pas l'ordre de départ de la liste
On peut utiliser la
méthode ForAll() sur le retour de AsParallel().Where()

PLinq (Parallel with Linq)
Voici un exemple de code ou on utilise PLinq mais on force le fait de respecter l'ordre de la liste de départ avec AsOrdered() puis un nouveau ForEach() dessus.
On ajoute la méthode
AsOrdered() pour garder l'ordre des entrées, mais c'est pas tout
Ici, la liste de départ est respectée. A noter que si vous êtes en .Net Framework < 7, la durée d'exécution sera plus longue sur ce cas. Sinon elle sera plus rapide car il y a eu de grosses optimisations en Framework 7.0 sur le PLinq
On doit retransformer le retour de AsParallel().AsOrdered().
Puis, on transforme en liste, puis on fait un ForEach

PLinq (Parallel with Linq)
Voici un autre exemple de code un peu plus complet pour l'utilisation de PLinq.
De nouveau, ici la l'ordre de la liste de départ n'est pas respecté.

PLinq (Parallel with Linq)
Encore un autre exemple de code où ici on combine PLinq et l'utilisation de Task
De nouveau, ici la l'ordre de la liste de départ n'est pas respecté.

On utilise la méthode
ContinueWith() sur la liste
pour effectuer une opération
asynchrone
Nous sommes contraint
de réaliser un foreach classique afin de pouvoir se référer à task.Result
Exercice class PLinq
Une partie de ces exercices sont aussi présents dans les slides, mais ils ont été légèrement adapté pour un peu plus vous forcer à réfléchir durant la pratique.
Posez des questions si des choses ne sont pas claires pour vous
Envoyer Exercice2_PP_PLinq.md
Apparté Exception & parallélisme
Il est important de savoir que la première exception levée stoppera l'exécution de votre code si vous utilisez les Exceptions classiques dans votre code.

Ici, nous simulons les exception (tests) mais comme ça, seule la première exception est récupérée
Apparté Exception & parallélisme
Il est important de savoir que la première exception levée stoppera l'exécution de votre code si vous utilisez les Exceptions classiques dans votre code.


En créant votre propre classe d'exception, vous pouvez récupérer toutes les exceptions pour toutes les tâches
TaskCompletionSource
Honnêtement, je n'utilise pas trop cette classe, mais autant la voir rapidement pour en voir un exemple (sans faire d'exo)

Une partie ou l'on génère un résultat
TaskCompletionSource
Honnêtement, je n'utilise pas trop cette classe, mais autant la voir rapidement pour en voir un exemple (sans faire d'exo)
Une partie ou l'on génère une exception intentionnellement

Exercices plus complexe de programmation parallèle
Petit tutoriel sympa à suivre si on a le temps...
Exercice plus complet avec la préparation d'un petit déjeuné


Concrètement, je vais vous envoyer le code non asynchrone, vous allez devoir le rendre asynchrone, selon les captures ci-desous.
Conseil pratique : Vous pouvez utiliser des Tasks et utiliser WhenAny() pour bien implémenter tout ça
Corrigé de l'exercice avec la préparation d'un petit déjeuné


L'évaluation ressemblera assez à ce type de traitements parallèles, donc je vous enverrai le corrigé et prenez le temps de l'analyser / poser des questions
Mise en place d'une API en utilisant la programmation Parallèle
Pourquoi faire une API en asynchrone ?
Il existe plusieurs avantages à mettre en place une API de manière asynchrone :
- Permettre de réaliser des appels en parallèle depuis un code client (Nous allons créer une API et utiliser un projet Console pour remplir les informations d'une formation pour voir un exemple simple)
- Permettre au serveur contenant l'API de travailler sur plusieurs processeurs avec des process multiples (Ceci peut donc surcharger le CPU du serveur, mais peut aussi grandement améliorer les performances de votre API)
- Aujourd'hui, en .Net toutes les API sont asynchrones, ou alors elles permettent de réaliser les deux.
Mettre en place une API en .Net
Il existe plusieurs façons de mettre en place une API en .Net, nous allons en utiliser une assez simple en utilisant FastEndpoint. (A ma connaissance, il faut avoir dans outils => obtenir les outils et fonctionnalités => Développement web et ASP.Net)
Petite démonstration rapide de comment mettre en place une API avec FastEndpoint + Parallélisation des appels
Documentation FastEndpoint (si besoin)
Mettre en place une API en .Net
Nous allons réaliser les étapes ensemble pour un simple test, puis vous aurez un exercice à réaliser qui s'en rapproche afin de vraiment tester tout ça.
Etapes à suivre :
- Créer un nouveau projet ASP.Net Web Application (nommez le "SocialsEndpoint"
- Ajoutez le package Nuget "FastEndpoint"
- Cleaner le Program.cs en le remplaçant par ce qui suit :
using FastEndpoints;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFastEndpoints();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseFastEndpoints();
app.Run();
Mettre en place une API en .Net
Nous allons réaliser les étapes ensemble pour un simple test, puis vous aurez un exercice à réaliser qui s'en rapproche afin de vraiment tester tout ça.
Etapes à suivre :
- Modifier le appsettings.json en rajoutant le noeud Kestrel comme suit :
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5000"
},
"Https": {
"Url": "https://localhost:5001"
}
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Mettre en place une API en .Net
Nous allons réaliser les étapes ensemble pour un simple test, puis vous aurez un exercice à réaliser qui s'en rapproche afin de vraiment tester tout ça.
Etapes à suivre :
- Créer un dossier "Controller" puis ajoutez une classe vide cette classe hériter de "EndpointWithoutRequest" et doit override deux méthodes Configure & HandleAsync(CancellationToken ct)
public class EndpointTest : EndpointWithoutRequest
{
public override void Configure()
{
Get("api/test");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken tc)
{
await Task.Delay(1000);
await SendAsync(new
{
test = "test"
}, cancellation: tc);
}
}
Mettre en place une API en .Net
Nous allons réaliser les étapes ensemble pour un simple test, puis vous aurez un exercice à réaliser qui s'en rapproche afin de vraiment tester tout ça.
Etapes à suivre :
- Démarrer votre projet web ! Votre API doit fonctionner à cette étape (vous pouvez tester via postman (https://localhost:5001/api/test doit retourner "test")
- Il ne reste plus qu'à faire l'appel depuis le projet Console (un nouveau ou un utilisé auparavant)
- (Et une étape à la slide suivante côté projet Console pour GetTest(client))
// code in Program.cs
HttpClient client = new HttpClient();
var testTask = ApiAsyncMethods.GetTest(client);
var testResult = await testTask;
Console.WriteLine("Test result value {0}", testResult);
Attention, j'ai fait
exprès ici de ne pas vous donner les lignes de code pour l'asynchrone
Mettre en place une API en .Net
Nous allons réaliser les étapes ensemble pour un simple test, puis vous aurez un exercice à réaliser qui s'en rapproche afin de vraiment tester tout ça.
Etapes à suivre :
- Donc dernière étape : L'appel asynchrone et le mapping des données dans une classe, peu importe, pour l'exemple, j'ai une classe static avec une méthode static
internal class TestFastEndpoint
{
public string test { get; set; }
}
public static async Task<string> GetTest(HttpClient client)
{
HttpResponseMessage response = await client.GetAsync("https://localhost:5001/api/test");
var jsonContent = await response.Content.ReadAsStringAsync();
var test = JsonSerializer.Deserialize<TestFastEndpoint>(jsonContent);
return test.test; // Oui, c'est pas très beau mais c'est un test !
}
Mettre en place une API en .Net
Exercice
- Pour cet exercice, vous allez devoir implémenter 3 nouveaux endpoints
- FacebookFollowers (retour un nombre en dur de followers en entier)
- InstagramFollowers (de même)
- TwitterFollowers (de même)
- Votre objectif est de faire 3 méthodes contenant un retour de followers, chacun des méthodes doit faire un await Task.Delay(1000); comme vu dans l'exemple test. Et retourner une valeur en dur d'entier dans HandleAsync via SendAsync()
- Côté projet Console, votre but est de remplir les informations d'une classe "User" (Name (setté par le constructeur), FacebookFollowers, TwitterFollowers, InstagramFollowers) en utilisant la programmation asynchrone (Task.WhenAll par exemple) pour réaliser l'opération de remplissage des infos de l'utilisateurs en 1 seconde (et non 3). Il vous faut donc un timer
- Vous devez afficher les informations de l'utilisateur dans la console après les avoir récupérées via une méthode override ToString() dans la classe User.
- Pour ceux qui veulent aller plus loin regardez la doc de FastEndpoint et essayez de faire des Endpoints avec Request + Response, c'est pas si dur ;)
Un exemple de code avec l'UI
- // TODO : Add a sample of use case in .Net with an interface waiting for a async task to be done without being frozen
Programmation parallèle avec Node.js
L'asynchronisme en JS
Nous allons parler d'asynchronisme et faire des exemples en Node.js, mais les principes sont similaires en js, node, typescript,...
Ce n'est pas vraiment le but de la formation d'être complet sur l'asynchronisme en JS, mais nous allons couvrir 3 topics principaux :
- Les callbacks et pourquoi on ne les utilise plus
- Les Promises, leurs avantages et possibilités
- L'utilisation d'async / await et comment les utiliser efficacement
J'espère que cette introduction suffira à vous mettre le pied à l'étrier afin d'aller chercher plus d'info par vous même dans les exercices proposés...
Les callbacks
Si vous êtes familier avec javascript, vous connaissez les callbacks, c'est un moyen de passer une fonction en paramètre d'une autre fonction pour qu'elles soient exécutées une fois qu'une opération asynchrone est terminée.
const fs = require('fs');
fs.readFile('file.txt', 'utf-8',
(err, data) => {
if (err) {
console.error("Error :", err);
return;
}
console.log("File content :", data);
}
);
Ce troisième paramètre, c'est mon callback
La syntaxe est pas folle de base...
Les callbacks
On peut donc créer autant de callbacks qu'on veut et les enchaîner comme on le souhaite, voyons un exemple plus complet.
Montrer l'exemple CallBackHell
Dans cet exemple, on voit que ça peut vite devenir complexe de maîtriser la pyramide de Doom ou ce qui est nommé callback hell.
C'est pour cette raison que les Promises ont vu le jour et nous allons plutôt nous attarder là dessus !
Et encore... On n'a pas vu comment gérer les erreurs dans cet exemple... Et on ne va pas perdre le temps de le faire.
🤮
Les Promises
Les promises sont le meilleur moyen de mieux gérer ces problèmes d'asynchronisme et de callback hell de js.
Créer une Promise
Appeler une Promise (v1)
const promise = new Promise((resolve) => {
// Simulate an asynchronous operation
setTimeout(() => {
// Resolve the promise
resolve('Promise resolved')
}, 2000)
})
promise.then((result) => {
console.log(result)
})
On créer un objet de type Promise, on lui passe resolve en argument et on peut appeler resolve pour finaliser notre opération promise.
On appelle la promise et avec then((result) => {...}) on attend le résultat pour effectuer l'affichage
Ce qui se passe dans le then se passe après le retour de la promise
Ce qui est resolve est passé en paramètre dans le then
Les Promises
On a également la possibilité de gérer les cas d'échecs, avec reject et le catch.
Créer une Promise
Appeler une Promise (v1)
const rejectedPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const random = Math.round(Math.random())
if (random)
resolve('Promise resolved')
else
reject('Promise rejected')
}, 2000)
})
rejectedPromise.then((result) => {
console.log(result)
}).catch((error) => {
console.error(error)
})
Le random ne sert qu'à pouvoir tester les cas, l'idée est de gérer les cas de réussite avec resolve et les cas d'échecs avec reject.
Cette fois on peut ajouter le catch pour gérer le cas où on est passé dans le reject et qu'on a voulu gérer une erreur
Les Promises
Promise.Any() : Retourne la première promise qui a été résolue, ceci permettant de gérer certains workflow particuliers
Promise.Any()
import fetch from 'node-fetch'
const promisePokemonOne = fetch('https://pokeapi.co/api/v2/pokemon/ditto')
const promisePokemonTwo = fetch('https://pokeapi.co/api/v2/pokemon/pikachu')
const firstResult = Promise.any([promisePokemonOne, promisePokemonTwo])
firstResult
.then((response) => response.json())
.then((data) => {
console.log(`Pokemon type : ${data.types[0].type.name}`)
})
.catch((error) => {
console.error('Error fetching Pokemon data:', error)
})
Ici, le résultat dépendra de chaque exécution, il sera donc différent d'une exécution à l'autre
Attention ici, fetch retourne une promise et il nous faut le resultat json()
Les Promises
Promise.All() : Attend que toutes les promises passées soient résolues avant de pouvoir chaîner sur les résultats
Promise.All()
const promiseOne = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise one resolved')
}, 2000)
})
const promiseTwo = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise two resolved')
}, 1000)
})
// This will wait for all promises to be resolved
const allPromises = Promise.all([promiseOne, promiseTwo])
// We can then use the results of all promises that comes as an array
allPromises
.then((results) => {
console.log(`1 : ${results[0]}`)
console.log(`2 : ${results[1]}`)
})
On va donc pouvoir itérer sur chaque promise les unes après les autres lorsqu'elles seront TOUTES terminées de manière asynchrone.
Les Promises
Promise.All() : Attend que toutes les promises passées soient résolues avant de pouvoir chaîner sur les résultats
Promise.All()
import fetch from 'node-fetch'
const promisePokemonOne = fetch('https://pokeapi.co/api/v2/pokemon/ditto')
const promisePokemonTwo = fetch('https://pokeapi.co/api/v2/pokemon/pikachu')
const allResults = Promise.all([promisePokemonOne, promisePokemonTwo])
allResults
.then((responses) => Promise.all(responses.map((response) => response.json())))
.then((data) => {
console.log(`Pokemon types : ${data[0].types[0].type.name} and ${data[1].types[0].type.name}`)
})
.catch((error) => {
console.error('Error fetching Pokemon data:', error)
})
Juste un exemple supplémentaire utilisant l'API pokemon, notez la particularité concernant le .map utilisé sur le responses...
Les Promises
Un exemple "concret" de mise en place de Promise et de les chaîner tout en ayant la possibilité de faire autre chose en même temps
Voir PromisesPizza (code)
Pizza
Laundry
Order (2s)
Bake (2s)
Receive (2s)
Eat(2s)
DoLaundry (7s)
Total time
8 secondes
Async / Await
Async & await sont des mots clés importants dans l'asynchronisme en javascript. Ils seront bien souvent préférés à l'utilisation de then / catch pour des plusieurs raisons que l'on listera dans la slide suivante.
Async / await
function getRandomNumber() {
return new Promise((resolve) => {
setTimeout(() => {
const random = Math.floor(Math.random() * 10) + 1
resolve(random)
}, 2000)
})
}
async function main() {
try {
const randomNumber = await getRandomNumber()
console.log(`Random number is ${randomNumber}`)
} catch (error) {
console.error(`Error: ${error}`)
}
}
main()
Dans un contexte de fichier index.js on ne peut pas directement utiliser async / await, c'est pourquoi on créer une méthode "main", on la met en async et on peut utiliser await dedans.
+ Des try / catchs classiques. Et c'est tout !
Async / Await
Quelques remarques sur async / await. Et donc pourquoi choisir plutôt ce moyen en amont des then / catch.
Permet une syntaxe plus propre, du code plus clean |
Async / await doivent toujours être utilisés ensemble |
Async / await ne concerne que le receveur d'infos |
On peut await n'importe quelle promise ! |
Une méthode async simple retourne une promise en JS |
N'importe quelle méthode peut être mise async |
Améliore et clarifie la gestion d'erreurs avec les try / catch |
Async / Await
Voici exactement le même exemple que précédemment, mais cette fois, c'est fait en utilisant les mots clés async / await
Voir PromisesPizzaAsync (code)
Pizza
Laundry
Order (2s)
Bake (2s)
Receive (2s)
Eat(2s)
DoLaundry (7s)
Total time
8 secondes
Exercices Promises
Premier graphe simple

Il faut donc que
- A, puis B, puis C soient exécutés et en parallèle
- D, puis E puis F.
Un simple console log de la lettre suffit.
Voici trois exercices que vous pouvez réaliser pour pratiquer les Promises en le faisant proprement avec l'appels en async / wait.
Vous avez trois graphes à implémenter (voir les 3 slides d'exercices)
Exercices Promises
Voici trois exercices que vous pouvez réaliser pour pratiquer les Promises en le faisant proprement avec l'appels en async / wait.
Vous avez trois graphes à implémenter (voir les 3 slides d'exercices)
Second graphe un peu plus complexe
Il faut donc que
- A et B soient exécutés en parallèle avant d'exécuter C
- Après C, on exécute D
Un simple console log de la lettre suffit.

Exercices Promises
Voici trois exercices que vous pouvez réaliser pour pratiquer les Promises en le faisant proprement avec l'appels en async / wait.
Vous avez trois graphes à implémenter (voir les 3 slides d'exercices)
Troisième graphe un peu plus casse tête...
Il faut donc que
- A s'exécute en premier
- Puis B, C et D s'exécutent en parallèle
- Puis E et F s'exécutent en parallèle
Un simple console log de la lettre suffit.

Exercices en typescript
Vous pourrez vous exercer et aller plus loin via ce lien github. Les principes qui y sont présentés sont plus avancés que ce qui a été vu dans le lien précédent. (C'est du typescript ici, mais c'est le lien le plus intéressant et complet que j'ai pu trouver)
Ce lien est vraiment très complet et les exercices sont incroyables pour aller plus loin dans les notions de programmation parallèle avec js. Je vous conseille vivement de faire le maximum d'exercices.
Evaluation
Partie pratique
BON COURAGE !
Evaluation
Réaliser un workflow asynchrone.
Envoyer le fichier Evaluation_PP.md
+ Eval_file1.txt
+ Eval_file2.txt
Calcul somme des nombres de 1 -> 3000
Traiter 2 fichiers calculer le nombre de mots
Traiter 2 fichiers calculer le nombre de "Lorem"
Afficher le résultat de la somme totale des 5 résultats
Affichage console
Affichage console
Affichage console
N'oubliez pas d'afficher le temps de tout ce traitement et le résultat final !
Utilisez Enumerable.Range(1, 3000)
Partie pratique
BON COURAGE !
Evaluation rattrapage
Réaliser un workflow asynchrone. (Seul le traitement des fichiers s'effectue de manière asynchrone)
Envoyer le fichier Evaluation_PP_rattrapage.md
+ Eval_file1_rattrapage.txt
+ Eval_file2_rattrapage.txt
Calcul somme des nombres de 1 -> 10000
Traiter 2 fichiers calculer le nombre de mots
Traiter 2 fichiers calculer le nombre de "Lorem"
Afficher le résultat de la somme totale des 6 résultats
Affichage console
Affichage console
Affichage console
N'oubliez pas d'afficher le temps de tout ce traitement et le résultat final !
Faire un appel à l'API et compter le nombre de mot du retour
Parallèle programming in C#
By Cėdric Brasseur
Parallèle programming in C#
- 468