Ah!ah! Mon dieu que c’est drôle. Donc le sujet du jour est l’usage du sémaphore.
Le problème à la base est le suivant: J’utilise la TPL (Task Parallel Libray) de .NET pour effectuer des requêtes WEB en parallèle. Le code en question donne donc:
foreach (IHTMLElement el in (IHTMLElementCollection)document.body.all)
{
if (el.tagName == "A")
{
string uri = el.getAttribute("href").ToString();
tasks.Add(Task.Factory.StartNew(() => Process(uri)));
}
}
Task.WaitAll(tasks.ToArray());
Dans la méthode Process, il y a principalement l’appel à une URI pour récupérer une image:
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
WebResponse webResponse = webRequest.GetResponse();
Grâce à la TPL, mon code va donc être exécuté et schedulé par le framework et je vais pouvoir profiter à fond de tous les coeurs de mon ordinateur (ordinateur de bourgeois, il faut bien l’admettre car il contient 8 coeurs).
Notons au passage que j’ai ici fait le choix de faire explicitement des tasks mais que j’aurai pu écrire le même code sans les tasks en utilisant Parallel.ForEach.
Toutefois, il y a un hic. En effet, le site que je requête ne permet que 4 connexions en simultanée en provenance d’une même adresse IP. Or, ici je vais avoir de nombreuses tâches qui vont se lancer et qui vont donc générer de nombreuses connexions web. Le problème c’est que les quatre premières vont bien passer mais les suivantes vont être mises en attente par le serveur et risqueront très probablement de partir en timeout.
Il faut donc que je contrôle mes tasks (ou mon Parallel.ForEach) pour que les ordres ne soit pas plus de 4 à s’exécuter à la fois.
On pourrait bien sûr (comme je le vois si souvent sur les superbes architectures que je croise un peu partout) monter une usine à gaz monstrueuse qui s’occuperait de séquencer les jobs, de mettre en attente les envois de tasks, etc..
Mais on pourrait aussi être pragmatique et utiliser les outils à notre disposition. Et c’est là qu’entre en jeu le sémaphore (tadammmm!!!).
Ce dernier (et ceci depuis la nuit des temps, au moins) permet de contrôler l’accès à des ressources en nombre limité. Et c’est exactement ce que j’ai ici : je ne peux avoir que 4 requêtes en simultanées. Je vais donc juste déclarer un sémaphore ayant 4 ressources:
Semaphore semaphore = new Semaphore(4, 4);
Puis dans ma méthode Process (celle donc qui est exécutée par les tasks), je vais juste rajouter deux lignes (une tout au début et une tout à la fin) qui auront pour but de contrôler l’accès à la ressource:
semaphore.WaitOne();
...
semaphore.Release();
Ainsi, les 4 premières tasks passeront sans problème la barrière du WaitOne (qui décrémentera en interne le nombre de ressources disponibles) et à partir de la 5ème task, l’appel sera bloqué (de manière passive bien sur).
Dès qu’une task arrive sur le Release, elle libérera une ressource débloquant par la même une task en attente. Ainsi j’ai bien la garantie que mes requêtes web ne partiront pas en timeout et ceci avec un impact minimal 
Donc vive TPL et les sémaphores!
.Net, TPL
Parallélisme