Vous êtes sur la page 1sur 63

Partie 6 Cration de solutions professionnelles

Partie VI

Cration de solutions professionnelles


Dans cette partie : Introduction la Bibliothque parallle de tches Accs parallle aux donnes Cration et utilisation dun service Web

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

Chapitre 27

Introduction la Bibliothque parallle de tches (TPL)


Au terme de ce chapitre, vous saurez :
Dcrire les bnfices de limplmentation doprations parallles dans une application. Expliquer comment la Bibliothque parallle de tches fournit une plateforme optimale pour implmenter des applications qui tirent profit dun processeur multicurs. Utiliser la classe Task pour crer et excuter des oprations parallles dans une application. Utiliser la classe Parallel pour parallliser certaines constructions de programmation courantes. Utiliser des tches avec des threads pour amliorer la ractivit et le dbit dans des applications graphiques. Annuler des tches de longue dure et grer des exceptions leves par des oprations parallles.

Vous avez appris utiliser Microsoft Visual C# pour construire des applications qui fournissent une interface utilisateur graphique et qui peuvent grer les informations stockes dans une base de donnes. Ces fonctionnalits sont courantes dans la plupart des systmes modernes. Cependant, les exigences des utilisateurs ont volu au mme rythme que la technologie, si bien que les applications qui permettent daccomplir les tches quotidiennes doivent fournir des solutions toujours plus sophistiques. Dans la dernire partie de ce livre, vous verrez quelques-unes des fonctionnalits avances introduites dans le .NET Framework 4.0. En particulier, dans ce chapitre, vous verrez comment amliorer les traitements simultans dans une application grce la Bibliothque parallle de tches. Au chapitre suivant, vous verrez comment les extensions parallles fournies avec le .NET Framework peuvent tre utilises en conjonction avec LINQ (Language

Partie 6 Cration de solutions professionnelles

Integrated Query) pour amliorer le dbit des oprations daccs aux donnes. Dans le dernier chapitre, vous aborderez Windows Communication Foundation pour construire des solutions distribues qui peuvent incorporer des services sexcutant sur plusieurs ordinateurs. En guise de bonus, lannexe dcrit comment utiliser le Dynamic Language Runtime pour construire des applications C# et des composants qui peuvent interoprer avec des services crs avec dautres langages oprant en dehors de la structure fournie par le .NET Framework, tels que Python et Ruby. Dans les prcdents chapitres de ce livre, vous avez appris utiliser C# pour crire des programmes qui sexcutent dans un seul thread, ce qui signifie qu un moment donn, un programme nexcute quune seule instruction. Cette approche nest pas toujours la plus efficace. Par exemple, vous avez vu dans le chapitre 23, Saisie utilisateur , que si votre programme attend que lutilisateur clique sur un bouton dans un formulaire WPF (Windows Presentation Foundation), il peut accomplir une autre tche pendant ce temps-l. Cependant, si un programme mono-thread doit effectuer un long calcul sollicitant beaucoup le processeur, il ne peut pas ragir quand lutilisateur saisit des donnes dans un formulaire ou clique sur un menu. Lutilisateur a alors limpression que lapplication a plant. Ce nest que lorsque le calcul est termin que linterface utilisateur ragit nouveau. Les applications qui peuvent effectuer plusieurs tches en mme temps optimisent les ressources disponibles sur un ordinateur, sexcutent plus rapidement et sont plus ractives. De plus, certaines tches individuelles peuvent sexcuter bien plus rapidement si on les divise en chemins dexcution parallles tournant de faon simultane. Dans le chapitre 23, vous avez vu comment WPF peut tirer profit des threads pour amliorer la ractivit dune interface utilisateur graphique. Dans ce chapitre, vous allez apprendre utiliser la Bibliothque parallle de tches pour implmenter une forme plus gnrique de traitement multitche dans vos programmes qui peut sappliquer aux applications de calcul intensif et pas seulement celles grant des interfaces utilisateur.

Pourquoi grer le mode multitche par un traitement parallle ?


Comme nous lavons dj voqu dans lintroduction, il y a deux raisons Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

principales qui motivent le choix du mode multitche dans une application : Amlioration de la ractivit Vous pouvez donner lutilisateur limpression que le programme effectue plus dune tche la fois en divisant le programme en threads dexcution concurrents et en permettant chaque thread de sexcuter tour de rle pendant une courte priode de temps. Il sagit du modle conventionnel coopratif que connaissent bien les dveloppeurs Windows chevronns. Cependant, ce nest pas vritablement du mode multitche car le processeur est partag entre les threads, et la nature cooprative de cette approche ncessite que le code excut par chaque thread se comporte de manire approprie. Si un thread accapare le CPU et les ressources disponibles au dtriment des autres threads, cette approche na plus aucun intrt. Il est parfois difficile dcrire des applications performantes qui suivent ce modle de faon cohrente. Amlioration de lvolutivit Vous pouvez amliorer lvolutivit grce une gestion efficace des ressources de traitement disponibles et en utilisant ces ressources pour rduire le temps ncessaire lexcution des parties dune application. Un dveloppeur peut dterminer quelles parties dune application peuvent tourner en parallle et faire en sorte quelles sexcutent simultanment. Plus les ressources informatiques augmentent, plus les tches peuvent sexcuter en parallle. Jusqu un pass rcent, ce modle ne convenait quaux systmes qui possdaient plusieurs CPU ou taient capables de rpartir le traitement sur plusieurs ordinateurs en rseau. Dans les deux cas, on devait utiliser un modle capable de coordonner les tches parallles. Microsoft fournit une version spcialise de Windows appele High Performance Compute (HPC) Server 2008, qui permet une entreprise de crer des clusters de serveurs pouvant distribuer et excuter des tches en parallle. Les dveloppeurs peuvent utiliser limplmentation Microsoft du MPI (Message Passing Interface), un protocole de communication indpendant des langages rput, pour concevoir des applications bases sur des tches parallles qui se coordonnent et cooprent les unes avec les autres en senvoyant des messages. Les solutions bases sur Windows HPC Server 2008 et le MPI sont idales pour des applications scientifiques et industrielles de grande envergure, mais elles sont coteuses pour des microordinateurs sous dimensionns. Dans ces conditions, vous pouvez penser que la manire la plus rentable de concevoir des solutions multitches pour des applications bureautiques est

Partie 6 Cration de solutions professionnelles

lapproche cooprative multithread. Cependant, cette approche ntait conue que pour offrir de la ractivit (les ordinateurs avec un seul processeur pouvaient garantir que chaque tche possdait une part quitable du processeur). Cela ne convient pas pour les machines multiprocesseurs car ce nest pas conu pour rpartir la charge entre les processeurs et, par consquent, cela est peu volutif. Quand les machines de bureau multiprocesseurs cotaient cher (elles taient donc relativement rares), ce ntait pas un problme. Cependant, la situation a chang, comme je vais lexpliquer brivement.

Apparition des processeurs multicurs


Il y a dix ans, le cot dun ordinateur personnel standard se situait dans une fourchette allant de 500 1000 euros. Aujourdhui, un ordinateur personnel correct cote toujours peu prs le mme prix, mme aprs dix ans dinflation. Un PC typique de nos jours comprend un processeur sexcutant une vitesse de 2 3 GHz, un disque dur de 500 Go, 4 Go de RAM, une carte vido haute rsolution, et un graveur DVD. Il y a dix ans, les caractristiques dune machine standard taient un processeur sexcutant une vitesse de 500 MHz 1 GHz, un disque dur de 80 Go et Windows sexcutait assez bien avec 256 Mo de RAM, les graveurs de CD cotant plus de 100 euros (les graveurs de DVD taient rares et extrmement chers). Ce sont les joies du progrs technologique : du matriel plus rapide et plus puissant des prix de plus en plus bas. Ceci nest pas une nouvelle tendance. En 1965, Gordon E. Moore, co-fondateur dIntel, crivit un article intitul Cramming more components onto integrated circuits , qui expliquait pourquoi la miniaturisation croissante des composants permettait un plus grand nombre de transistors dtre intgrs sur une puce. En 1975, la baisse des cots de production combine une technologie plus accessible permettait lassemblage de 65 000 composants sur une seule puce. Les observations de Moore ont engendr ce que lon appelle la loi de Moore qui stipule que le nombre de transistors dun circuit intgr augmente de faon exponentielle, doublant approximativement tous les deux ans (en fait, Gordon Moore tait au dpart encore plus optimiste que cela, car il postulait que le volume des transistors doublerait tous les ans, mais il modifia ultrieurement ses calculs). La possibilit dagrger des transistors engendra une transmission des donnes plus rapide entre les composants. Cela signifiait quon pouvait sattendre ce que les fondeurs de puces produisent des microprocesseurs plus rapides et Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

plus puissants un rythme presque sans relche, ce qui permettrait aux dveloppeurs dapplications dcrire des logiciels encore plus compliqus qui sexcuteraient plus rapidement. La loi de Moore concernant la miniaturisation des composants lectroniques est toujours valable, mme aprs plus de 40 ans. Cependant, la physique a commenc intervenir. Il y a une limite o il nest pas possible de transmettre plus rapidement des signaux entre les transistors sur une seule puce, quelle que soit la finesse de gravure ou la densit. Pour un dveloppeur de logiciel, le rsultat le plus notable de cette limite est que les processeurs ont cess dtre plus rapides. Il y a six ans, un processeur rapide tournait 3 GHz. Aujourdhui, un processeur rapide sexcute toujours 3 GHz. La limite de la vitesse laquelle les processeurs peuvent transmettre des donnes entre les composants a amen les fondeurs de puces chercher dautres voies pour accrotre la quantit de travail quun processeur peut raliser. Le rsultat est que les processeurs les plus modernes ont dsormais deux curs voire plus. Les fabricants de puces ont mis plusieurs processeurs sur la mme puce et ont ajout la logique ncessaire pour leur permettre de communiquer et de se coordonner les uns avec les autres. Les processeurs dual-core (deux curs) et les processeurs quad-core (quatre curs) sont devenus courants aujourdhui. Les puces avec 8, 16, 32, et 64 curs sont disponibles, et on sattend ce que leur prix chute dans un futur proche. Par consquent, bien que les processeurs aient cess daugmenter leur vitesse, on peut sattendre en avoir plus sur une mme puce. Quest-ce que cela signifie pour un dveloppeur dapplications C# ? Avant les processeurs multicurs, une application mono-thread ne pouvait tre acclre quen sexcutant sur un processeur plus rapide. Avec les processeurs multicurs, ce nest plus le cas. Une application mono-thread sexcutera la mme vitesse sur un processeur single-core, dual-core, ou quad-core ayant la mme frquence dhorloge. La diffrence est que sur un processeur dual-core, un des curs du processeur sera presque inactif, et sur un processeur quad-core, trois des curs attendront du travail. Pour utiliser au mieux les processeurs multicurs, vous devez crire vos applications en tirant parti du mode multitche.

Partie 6 Cration de solutions professionnelles

Implmentation du mode multitche dans une application bureautique


Le mode multitche est la facult de faire plusieurs choses en mme temps. Cest un de ces concepts qui est facile dcrire mais qui, jusqu un pass rcent, tait difficile implmenter. Dans un scnario optimal, une application sexcutant sur un processeur multicurs effectue autant de tches concurrentes quil y a de curs de processeur disponibles, en occupant chacun des curs. Cependant, il y a de nombreux problmes prendre en considration pour implmenter la concurrence, notamment les questions suivantes : Comment peut-on diviser une application en un ensemble doprations concurrentes ? Comment peut-on sorganiser pour quun ensemble doprations sexcute en mme temps, sur plusieurs processeurs ? Comment peut-on assurer quon effectue autant doprations concurrentes quon a de processeurs disponibles ? Si une opration est bloque (elle attend par exemple la fin dune opration dentre/sortie), comment peut-on dtecter cela et sorganiser pour que le processeur excute une opration diffrente au lieu dtre inactif ? Comment peut-on dterminer quune ou plusieurs oprations concurrentes sont termines ? Comment peut-on synchroniser laccs aux donnes partages pour garantir que plusieurs oprations concurrentes ne corrompent pas par inadvertance leurs donnes ? Pour un dveloppeur dapplications, le premier problme est une question de conception dapplication. Les problmes restants dpendent de linfrastructure de programmation (Microsoft fournit la Bibliothque parallle de tches pour vous aider rsoudre ces problmes). Dans le chapitre 28, Accs parallle aux donnes , vous allez voir comment certains problmes de requtes possdent naturellement des solutions parallles, Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

et comment on peut utiliser le type ParallelEnumerable de PLINQ pour parallliser des oprations de requtes. Cependant, on a parfois besoin dune approche plus essentielle pour des situations plus gnralistes. La Bibliothque parallle de tches (TPL) contient une srie de types et doprations qui vous permettent de spcifier plus explicitement comment diviser une application en un ensemble de tches parallles.

Tches, threads et ThreadPool


Le type le plus important dans la TPL est la classe Task. La classe Task est une abstraction dune opration concurrente. On cre un objet Task pour excuter un bloc de code. On peut instancier plusieurs objets Task et dmarrer leur excution en parallle sil y a suffisamment de processeurs ou de curs de processeur disponibles.

Note partir de maintenant, jutiliserai le terme processeur pour faire rfrence soit un processeur single-core soit un seul cur de processeur sur un processeur multicurs.

En interne, la TPL implmente les tches et planifie leur excution en utilisant des objets Thread et la classe ThreadPool. Le multithreading et les pools de threads sont disponibles dans le .NET Framework depuis la version 1.0, et vous pouvez utiliser la classe Thread de lespace de noms System.Threading directement dans votre code. Cependant, la TPL fournit un degr suprieur dabstraction qui permet facilement de faire la distinction entre le degr de paralllisation dune application (les tches) et les units de paralllisation (les threads). Sur un ordinateur avec un seul processeur, ces lments sont habituellement les mmes. Cependant, sur un ordinateur avec plusieurs processeurs ou avec un processeur multicurs, ils sont diffrents. Si vous concevez un programme bas directement sur les threads, vous allez trouver que votre application ne peut pas voluer facilement ; le programme utilisera le nombre de threads que vous crez explicitement, et le systme dexploitation planifiera uniquement ce nombre de threads. Cela peut conduire une surcharge et des temps de rponse dgrads si le nombre de threads excde de beaucoup le nombre de processeurs disponibles, ou une gestion inefficace et un faible dbit si le nombre de threads est infrieur au nombre de processeurs. La TPL optimise le nombre de threads ncessaire pour implmenter un ensemble

Partie 6 Cration de solutions professionnelles

de tches concurrentes et les planifie de manire efficace en fonction du nombre de processeurs disponible. La TPL utilise un ensemble de threads fournis par le .NET Framework, appel ThreadPool, et implmente un mcanisme de file dattente pour distribuer la charge de travail entre ces threads. Quand un programme cre un objet Task, la tche est ajoute une file dattente globale. Quand un thread devient disponible, la tche est supprime de la file dattente globale, puis elle est excute par ce thread. ThreadPool implmente un certain nombre de mcanismes doptimisations et utilise un algorithme de vol de travail (work-stealing) pour sassurer que ces threads sont planifis de manire efficace.

Note ThreadPool tait disponible dans les ditions prcdentes du .NET Framework, mais il .NET Framework 4.0 afin de prendre en charge les objets Tasks.

Vous devez noter que le nombre de threads crs par le .NET Framework pour prendre en charge vos tches est nest pas ncessairement identique au nombre de processeurs. En fonction de la nature de la charge de travail, un ou plusieurs processeurs peuvent tre occups accomplir un travail prioritaire pour dautres applications et services. En consquence, le nombre optimal de threads de votre application peut tre infrieur au nombre de processeurs de lordinateur. Dun autre ct, un ou plusieurs threads dune application peuvent attendre la fin dune opration particulirement longue (accs mmoire, entre/sortie, rseau, etc.), ce qui rend disponibles les processeurs correspondants. Dans ce cas, le nombre optimal de threads peut tre suprieur au nombre de processeurs disponibles. Le .NET Framework adopte une stratgie itrative, appele algorithme descalade de colline (hill-climbing), pour dterminer de manire dynamique le nombre idal de threads de la charge de travail en cours. La chose importante retenir est que vous devez vous contenter dans votre code de diviser votre application en tches qui peuvent tre excutes en parallle. Le .NET Framework se charge de crer le nombre de threads appropri en se basant sur larchitecture des processeurs et la charge de travail de lordinateur, puis associe vos tches ces threads en les organisant pour quils soient excuts efficacement. Ce nest pas grave si vous divisez votre travail en trop de tches car le .NET Framework tentera de nexcuter que le nombre maximal de threads concurrents rellement possibles ; en fait, vous tes encourag surpartitionner votre travail car cela garantit que votre application pourra voluer si vous la Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

dplacez sur un ordinateur qui a plus de processeurs disponibles.

Cration, excution et contrle des tches


Lobjet Task et les autres types de la TPL rsident dans lespace de noms System.Threading.Tasks. On cre des objets Task avec le constructeur Task. Ce constructeur est surcharg, mais toutes les versions attendent la fourniture dun dlgu Action comme paramtre. Au chapitre 23, vous avez vu quun dlgu Action rfrence une mthode qui ne retourne pas de valeur. Un objet Task utilise ce dlgu pour excuter la mthode quand il est planifi. Lexemple suivant cre un objet Task qui utilise un dlgu pour excuter la mthode appele faireTravail (vous pouvez aussi utiliser une mthode anonyme ou une expression lambda, comme cela est suggr dans les commentaires du code) :
Task = new Task(new Action(faireTravail)); // Task = new Task(delegate { this.faireTravail(); }); // Task task = new Taskhis.faireTravail(); }); ... private void faireTravail() { // La tche excute ce code quand elle est dmarre ... }

Note Dans de nombreux cas, vous pouvez laisser le compilateur dduire le type de dlgu Action et spcifier simplement la mthode excuter. Par exemple, vous pouvez rcrire le premier exemple de la manire suivante :
Task task = new Task(faireTravail);

Les rgles dinfrence des dlgus implmentes par le compilateur ne sappliquent pas quau type Action, mais nimporte quel endroit o lon peut utiliser un dlgu. Nous verrons dautres exemples de ce type dans le reste de cet ouvrage.

Le type Action par dfaut rfrence une mthode qui naccepte pas de paramtre. Les autres surchargements du constructeur Task prennent un paramtre Action<objet> qui reprsente un dlgu qui fait rfrence une mthode acceptant un seul paramtre objet. Ces surchargements permettent de passer des donnes la mthode excute par la tche. Le code suivant illustre un exemple :
Action<objet> action;

Partie 6 Cration de solutions professionnelles

action = doWorkWithObject; objet parameterData = ...; Task task = new Task(action, parameterData); ... private void doWorkWithObject(objet o) { ... }

Aprs avoir cr un objet Task, vous pouvez lexcuter avec la mthode Start :
Task task = new Task(...); task.Start();

La mthode Start est aussi surcharge, et vous pouvez spcifier en option un objet TaskScheduler pour contrler le degr de concurrence et les autres options dordonnancement. Il est recommand dutiliser lobjet TaskScheduler par dfaut qui est intgr dans le .NET Framework ; vous pouvez aussi dfinir votre propre classe TaskScheduler si vous voulez exercer un contrle plus strict sur la manire dont les tches sont mises en file dattente et planifies. Les dtails de ces oprations dpassent le cadre de cet ouvrage, mais vous trouverez de plus amples informations sur ce sujet dans la description de la classe abstraite TaskScheduler qui est disponible dans la documentation de la bibliothque de classes du .NET Framework fournie avec Visual Studio. Vous pouvez obtenir une rfrence lobjet TaskScheduler par dfaut grce la proprit statique Default de la classe TaskScheduler. La classe TaskScheduler fournit aussi la proprit statique Current, qui retourne une rfrence lobjet TaskScheduler en cours dutilisation (cet objet TaskScheduler est utilis si vous ne spcifiez pas explicitement un planificateur). Une tche peut fournir des informations lobjet TaskScheduler par dfaut sur la manire de planifier et dexcuter la tche si vous spcifiez une valeur partir de lnumration TaskCreationOptions du constructeur Task. Pour de plus amples informations sur lnumration TaskCreationOptions, consultez la documentation de la bibliothque de classes du .NET Framework fournie avec Visual Studio. Quand la mthode excute par la tche est acheve, la tche se termine, et le thread utilis pour excuter la tche peut tre recycl pour excuter une autre tche. Normalement, le planificateur sorganise pour accomplir les tches en parallle Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

quand cela est possible, mais vous pouvez aussi planifier les tches en srie en crant une continuation. On ralise une continuation en appelant la mthode ContinueWith dun objet Task. Quand laction accomplie par lobjet Task se termine, le planificateur cre automatiquement un nouvel objet Task pour excuter laction spcifie par la mthode ContinueWith. La mthode spcifie par la continuation attend un paramtre Task, et le planificateur passe une rfrence la tche qui a achev la mthode. La valeur retourne par ContinueWith est une rfrence au nouvel objet Task. Lexemple de code suivant cre un objet Task qui excute la mthode faireTravail et spcifie une continuation qui excute la mthode faireAutreTravail dans une nouvelle tche quand la premire tche se termine :
Task task = new Task(faireTravail); task.Start(); Task newTask = task.ContinueWith(faireAutreTravail); ... private void faireTravail() { // La tche excute ce code quand elle est dmarre ... } ... private void faireAutreTravail(Task task) { // La continuation excute ce code quand faireTravail se termine ... }

La mthode ContinueWith est lourdement surcharge, et vous pouvez fournir un grand nombre de paramtres qui spcifient des lments supplmentaires, comme TaskScheduler pour utiliser une valeur TaskContinuationOptions. Le type TaskContinuationOptions est une numration qui contient un surensemble des valeurs de lnumration TaskCreationOptions. Parmi les valeurs supplmentaires, on peut citer : NotOnCanceled et OnlyOnCanceled Loption NotOnCanceled spcifie que la continuation ne doit sexcuter que si laction prcdente est termine et nest pas annule, et loption OnlyOnCanceled spcifie que la continuation ne doit sexcuter que si laction prcdente est annule. La section Annulation des tches et gestion des exceptions plus loin dans ce chapitre dcrit comment annuler une tche. NotOnFaulted et OnlyOnFaulted Loption NotOnFaulted indique que la

Partie 6 Cration de solutions professionnelles

continuation ne doit sexcuter que si laction prcdente est termine et ne lve pas une exception non gre. Loption OnlyOnFaulted provoque lexcution de la continuation seulement si laction prcdente lve une exception non gre. La section Annulation des tches et gestion des exceptions fournit plus dinformations sur la manire de grer des exceptions dans une tche. NotOnRanToCompletion et OnlyOnRanToCompletion Loption NotOnRanToCompletion spcifie que la continuation ne doit sexcuter que si la prcdente action ne se termine pas avec succs ; elle doit tre annule ou bien lever une exception. OnlyOnRanToCompletion provoque lexcution de la continuation seulement si laction prcdente se termine avec succs. Lexemple de code suivant montre comment ajouter une continuation une tche qui ne sexcute que si laction initiale ne lve pas une exception non gre :
Task task = new Task(faireTravail); task.ContinueWith(faireAutreTravail, TaskContinuationOptions.NotOnFaulted); task.Start();

Si vous utilisez habituellement le mme ensemble de valeurs TaskCreationOptions et le mme objet TaskScheduler, vous pouvez utiliser un objet TaskFactory pour crer et excuter une tche en une seule tape. Le constructeur de la classe TaskFactory permet de spcifier le planificateur de tches, les options de cration de tches, et les options de continuation de tches que les tches construites par cette fabrique doivent utiliser. La classe TaskFactory fournit la mthode StartNew pour crer et excuter un objet Task. Comme la mthode Start de la classe Task, la mthode StartNew est surcharge, mais toutes les deux attendent une rfrence une mthode que la tche doit excuter. Le code suivant illustre un exemple qui cre et excute deux tches utilisant la mme fabrique de tches :
TaskScheduler scheduler = TaskScheduler.Current; TaskFactory taskFactory = new TaskFactory(scheduler, TaskCreationOptions.None, TaskContinuationOptions.NotOnFaulted); Task task = taskFactory.StartNew(faireTravail); Task task2 = taskFactory.StartNew(faireAutreTravail);

Mme si vous ne spcifiez pas actuellement des options de cration de tches particulires et utilisez le planificateur de tches par dfaut, vous devez quand Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

mme envisager dutiliser un objet TaskFactory ; cela garantit la cohrence, et vous aurez moins de code modifier pour vous assurer que toutes les tches sexcutent de la mme manire si vous avez besoin de personnaliser le processus lavenir. La classe Task expose lobjet TaskFactory par dfaut utilis par la TPL via la proprit statique Factory. Vous pouvez lutiliser de la manire suivante :
Task task = Task.Factory.StartNew(faireTravail);

La synchronisation des tches est un besoin courant des applications qui invoquent des oprations en parallle. La classe Task fournit la mthode Wait qui implmente une simple mthode de coordination de tches. Elle permet de suspendre lexcution du thread en cours jusqu ce que la tche spcifie se termine :
task2.Wait(); // Attend que task2 se termine

Vous pouvez attendre un ensemble de tches en utilisant les mthodes statiques WaitAll et WaitAny de la classe Task. Ces deux mthodes prennent un tableau params contenant un ensemble dobjets Task. La mthode WaitAll attend jusqu ce que toutes les tches spcifies soient termines, et WaitAny attend jusqu ce quau moins une des tches spcifies se termine. On utilise ces mthodes de la manire suivante :
Task.WaitAll(task, task2); // Attend que task et task2 soient termines Task.WaitAny(task, task2); // Attend que task ou task2 soit termine

Utilisation de la classe Task pour implmenter le paralllisme


Dans lexercice suivant, vous allez utiliser la classe Task pour parallliser le code dune application qui sollicite beaucoup le processeur, et vous allez voir comment cette paralllisation rduit le temps dexcution de lapplication en rpartissant les calculs sur plusieurs curs de processeurs. Lapplication, appele GraphDemo, comprend un formulaire WPF qui utilise un contrle Image pour afficher un graphique. Lapplication dessine les points du graphique en accomplissant un calcul complexe.

Partie 6 Cration de solutions professionnelles

Note Les exercices de ce chapitre sont conus pour sexcuter sur un ordinateur dot dun processeur multicurs. Si vous ne possdez quun CPU single-core, vous nobservez pas les mmes effets. Vous ne devez pas non plus dmarrer dautres programmes ou services entre les exercices car cela peut affecter les rsultats constats.

Observation et excution de lapplication mono-thread GraphDemo


1. Dmarrez Microsoft Visual Studio 2010 sil nest pas dj ouvert. 2. Ouvrez la solution GraphDemo, situ dans le dossier \Visual C Sharp Etape par tape\Chapitre 27\GraphDemo de votre dossier Documents. 3. Dans lExplorateur de solutions, dans le projet GraphDemo, faites un double clic sur le fichier GraphWindow.xaml pour afficher le formulaire dans la fentre Design. Le formulaire contient les contrles suivants : Un contrle Image appel graphImage. Ce contrle image affiche le graphique affich par lapplication. Un contrle Button appel tracerButton. Lutilisateur clique sur ce bouton pour gnrer les donnes du graphique et les afficher dans le contrle graphImage. Un contrle Label appel duree. Lapplication affiche le temps mis pour gnrer et afficher les donnes du graphique dans ce label. 4. Dans lExplorateur de solutions, dveloppez GraphWindow.xaml, puis faites un double clic sur GraphWindow.xaml.cs pour afficher le code du formulaire dans la fentre Code. Le formulaire utilise un objet System.Windows.Media.Imaging.WriteableBitmap appel graphBitmap pour afficher le graphique. Les variables largeurPixel et hauteurPixel spcifient respectivement la rsolution horizontale et verticale de lobjet WriteableBitmap ; les variables dpiX et dpiY spcifient respectivement la densit horizontale et verticale de limage en points par pouce (dpi pour dots per inch) :
public partial class GraphWindow : Window

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches


{

private private private private private private

static long tailleMemoireDisponible = 0; int largeurPixel = 0; int hauteurPixel = 0; double dpiX = 96.0; double dpiY = 96.0; WriteableBitmap graphBitmap = null;

5. Examinez le constructeur GraphWindow. Il ressemble ceci :


public GraphWindow() { InitializeComponent(); PerformanceCounter compteurMem = new PerformanceCounter("Mmoire", "Octets disponibles"); tailleMemoireDisponible = Convert.ToUInt64(compteurMem.NextValue()); this.largeurPixel = (int)tailleMemoireDisponible / 20000; if (this.largeurPixel < 0 || this.largeurPixel > 15000) this.largeurPixel = 15000; this.hauteurPixel = (int)tailleMemoireDisponible / 40000; if (this.hauteurPixel < 0 || this.hauteurPixel > 7500) this.hauteurPixel = 7500;

Afin dviter davoir du code qui puise la mmoire disponible de votre ordinateur et gnre des exceptions OutOfMemory, cette application cre un objet PerformanceCounter qui interroge la quantit de mmoire physique disponible de lordinateur. On utilise alors cette information pour dterminer les valeurs appropries des variables largeurPixel et hauteurPixel. Plus vous avez de mmoire disponible sur votre ordinateur, plus les valeurs gnres pour largeurPixel et hauteurPixel sont grandes (dans les limites respectives de 15 000 et 7 500 pour chacune de ces variables) et plus vous verrez les bnfices de lemploi de la TPL au fur et mesure des exercices de ce chapitre. Cependant, si vous trouvez que lapplication gnre toujours des exceptions OutOfMemory, augmentez les diviseurs (20 000 et 40 000) utiliss pour gnrer les valeurs de largeurPixel et hauteurPixel. Si vous avez beaucoup de mmoire, les valeurs calcules pour largeurPixel et hauteurPixel peuvent dborder. Dans ce cas, elles contiendront des valeurs ngatives et lapplication chouera en gnrant plus tard une exception. Le code du constructeur vrifie ce cas et initialise les champs largeurPixel et

Partie 6 Cration de solutions professionnelles

hauteurPixel une paire de valeurs utiles pour permettre lapplication de sexcuter correctement dans cette situation. 6. Examinez le code de la mthode tracerButton_Click :
private void tracerButton_Click(objet sender, RoutedEventArgs e) { if (graphBitmap == null) { graphBitmap = new WriteableBitmap(largeurPixel, hauteurPixel, dpiX, dpiY, PixelFormats.Gray8, null); } int octetsParPixel = (graphBitmap.Format.BitsPerPixel + 7) / 8; int stride = octetsParPixel * graphBitmap.PixelWidth; int tailleDonnees = stride * graphBitmap.PixelHeight; byte [] donnees = new byte[tailleDonnees]; Stopwatch montre = Stopwatch.StartNew(); genererDonneesGraph(donnees); duree.Content = string.Format("Dure (ms) : {0}", montre.ElapsedMilliseconds); graphBitmap.WritePixels( new Int32Rect(0, 0, graphBitmap.PixelWidth, graphBitmap.PixelHeight), donnees, stride, 0); graphImage.Source = graphBitmap; }

Cette mthode sexcute lorsque lutilisateur clique sur le bouton tracerButton. Le code instancie lobjet graphBitmap sil na pas dj t cr par lutilisateur en cliquant sur le bouton tracerButton prcdemment, et il spcifie que chaque pixel reprsente une ombre grise, avec 8 bits par pixel. Cette mthode utilise les variables et mthodes suivantes : La variable octetsParPixel calcule le nombre doctets requis pour contenir chaque pixel (le type WriteableBitmap prend en charge une grande plage de formats de pixels, avec des pixels allant jusqu 128 bits pour les images en millions de couleurs). La variable stride contient la distance verticale, en octets, entre les pixels adjacents de lobjet WriteableBitmap. La variable tailleDonnees calcule le nombre doctets requis pour contenir les donnes de lobjet WriteableBitmap. Cette variable est utilise pour initialiser le tableau donnees avec la taille approprie. Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

Le tableau byte donnees contient les donnes du graphique. La variable montre est un objet System.Diagnostics.Stopwatch. Le type StopWatch est utile pour les oprations de chronomtrage. La mthode statique StartNew du type StopWatch cre une nouvelle instance dun objet StopWatch et dmarre son excution. Vous pouvez interroger le temps dexcution dun objet StopWatch en examinant la proprit ElapsedMilliseconds. La mthode genererDonneesGraph remplit le tableau donnees avec les donnes du graphique pour quelles soient affiches par lobjet WriteableBitmap. Vous allez examiner cette mthode dans la prochaine tape. La mthode WritePixels de la classe WriteableBitmap copie les donnes dun tableau byte dans un bitmap afficher. Cette mthode prend un paramtre Int32Rect qui spcifie la zone de lobjet WriteableBitmap remplir, les donnes utiliser pour copier vers lobjet WriteableBitmap, la distance verticale entre les pixels adjacents de lobjet WriteableBitmap, et un dcalage vers lobjet WriteableBitmap pour commencer y crire les donnes.

Note Vous pouvez utiliser la mthode WritePixels pour craser de manire slective les informations dun objet WriteableBitmap. Dans cet exemple, le code crase tout le contenu. Pour plus dinformations sur la classe WriteableBitmap, consultez la documentation de la bibliothque de classes du .NET Framework installe avec Visual Studio 2010.

La proprit Source dun contrle Image spcifie les donnes que le contrle Image doit afficher. Cet exemple dfinit la proprit Source avec la valeur de lobjet WriteableBitmap. 7. Examinez le code de la mthode genererDonneesGraph :
private { int int int void genererDonneesGraph(byte[] donnees) a = largeurPixel / 2; b = a * a; c = hauteurPixel / 2;

for (int x = 0; x < a; x ++) {

Partie 6 Cration de solutions professionnelles

int s = x * x; double p = Math.Sqrt(b - s); for (double i = -p; i < p; i += 3) { double r = Math.Sqrt(s + i * i) / a; double q = (r - 1) * Math.Sin(24 * r); double y = i / 3 + (q * c); tracerXY(donnees, (int)(-x + (largeurPixel / 2)), (int)(y + (hauteurPixel / 2))); tracerXY(donnees, (int)(x + (largeurPixel / 2)), (int)(y + (hauteurPixel / 2))); } } }

Cette mthode effectue une srie de calculs pour tracer les points dun graphique plutt complexe (le calcul en lui-mme na pas dimportance, il se contente de gnrer un graphique qui attire lil !). Au fur et mesure du calcul de chaque point, la mthode tracerXY est appele pour dfinir les octets appropris du tableau donnees qui correspond ces points. Les points du graphique se refltent autour de laxe X, si bien que la mthode tracerXY est appele deux fois pour chaque calcul : une fois pour la valeur positive de la coordonne X, et une fois pour la valeur ngative. 8. Examinez la mthode tracerXY :
private void tracerXY(byte[] donnees, int x, int y) { donnees[x + y * largeurPixel] = 0xFF; }

Cest une mthode simple qui dfinit loctet appropri dans le tableau donnees qui correspond aux coordonnes X et Y passs en paramtres. La valeur 0xFF indique que le pixel correspondant doit tre dfini blanc lorsque le graphique est affich. Tous les pixels qui ne sont pas dfinis sont affichs en noir. 9. Dans le menu Dboguer, cliquez sur Dmarrer sans dbogage pour gnrer et excuter lapplication. 10. Lorsque la fentre Graph Demo apparat, cliquez sur Tracer graphique, et attendez. Soyez patient sil vous plat. Lapplication met plusieurs secondes gnrer et afficher le graphique. Limage suivante illustre le graphique. Notez la Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

valeur du label Dure (ms) dans limage suivante. Dans ce cas, lapplication a mis 4 566 millisecondes (ms) pour tracer le graphique.

Note Lapplication a t excute sur un ordinateur avec 2 Go de mmoire et un Intel Core 2 Duo E6600 tournant 2,40 GHz. Vos temps peuvent varier si vous utilisez un processeur diffrent ou une quantit de mmoire diffrente. De plus, vous pouvez remarquer que cela semble plus long au dpart pour afficher le graphique que le temps affich. Cela sexplique par le fait que le temps pris pour initialiser les structures de donnes ncessaires pour afficher le graphique fait partie de la mthode WritePixels du contrle graphBitmap et se rajoute au temps pris pour calculer les donnes du graphique. Les excutions suivantes du programme nauront pas cette surcharge du processeur.

11. Cliquez sur Tracer graphique une fois encore, et notez la dure. Rptez cette action plusieurs fois pour obtenir une valeur moyenne. 12. Sur le bureau, faites un clic droit sur une zone vide de la barre de tches, puis dans le menu droulant, cliquez sur Dmarrer le Gestionnaire de tches.

Note Sous Windows Vista, la commande du menu droulant sappelle Gestionnaire de tches.

13. Dans le Gestionnaire de tches de Windows, cliquez sur longlet Performance. 14. Retournez dans la fentre Graph Demo puis cliquez sur Tracer graphique. 15. Dans le Gestionnaire de tches de Windows, notez la valeur maximum de lusage du CPU lorsque le graphique est gnr. Vos rsultats varieront, mais

Partie 6 Cration de solutions professionnelles

sur un processeur dual-core, lutilisation du CPU se situera probablement aux alentours de 5055 pour cent, comme cela est illustr dans limage suivante. Sur une machine quad-core, lutilisation du CPU sera probablement infrieure 30 pour cent.

16. Retournez dans la fentre Graph Demo, puis cliquez sur Tracer graphique nouveau. Notez la valeur de lusage du CPU dans le Gestionnaire de tches de Windows. Rptez cette action plusieurs fois pour obtenir une valeur moyenne. 17. Fermez la fentre Graph Demo et rduisez le Gestionnaire de tches de Windows. Vous avez prsent un point de comparaison du temps que lapplication met accomplir ses calculs. Il est cependant clair, si lon se base sur lutilisation du CPU affiche par le Gestionnaire de tches de Windows, que lapplication ne ralise pas le plein usage des ressources disponibles du processeur. Sur une machine dualcore, elle utilise un peu plus de la moiti de la puissance du CPU, et sur une machine quad-core elle nemploie quun peu plus du quart du CPU. Ce phnomne se produit car lapplication est mono-thread, et dans une application Windows, un thread unique ne peut employer quun seul cur dun processeur multicurs. Pour rpartir la charge sur tous les curs disponibles, vous devez diviser lapplication en tches et organiser chacune des tches pour quelle soit excute par thread spar tournant sur un cur diffrent.

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

Modification de lapplication GraphDemo en utilisant des threads parallles


1. Retournez dans Visual Studio 2010, et affichez le fichier GraphWindow.xaml.cs dans la fentre Code sil nest pas dj ouvert. 2. Examinez la mthode genererDonneesGraph. Si vous rflchissez bien, le but de cette mthode est de remplir les lments du tableau donnees. Elle parcourt le tableau grce la boucle externe for en se basant sur la variable de contrle de boucle x, qui est mise en valeur ici en gras :
private { int int int void genererDonneesGraph(byte[] donnees) a = largeurPixel / 2; b = a * a; c = hauteurPixel / 2;

for (int x = 0; x < a; x ++) { int s = x * x; double p = Math.Sqrt(b - s); for (double i = -p; i < p; i += 3) { double r = Math.Sqrt(s + i * i) / a; double q = (r - 1) * Math.Sin(24 * r); double y = i / 3 + (q * c); tracerXY(donnees, (int)(-x + (largeurPixel / 2)), (int)(y + (hauteurPixel / 2))); tracerXY(donnees, (int)(x + (largeurPixel / 2)), (int)(y + (hauteurPixel / 2))); } } }

Le calcul accompli par une itration de cette boucle est indpendant des calculs accomplis par les autres itrations. Par consquent, il est intressant de partitionner le travail accompli par cette boucle et dexcuter des itrations diffrentes sur un processeur spar. 3. Modifiez la dfinition de la mthode genererDonneesGraph pour accepter deux paramtres supplmentaires int appels partitionDebut et partitionFin, comme illustr en gras :
private void genererDonneesGraph(byte[] donnees, int partitionDebut, int partitionFin)

Partie 6 Cration de solutions professionnelles

{ }

...

4. Dans la mthode genererDonneesGraph, modifiez la boucle externe for pour aller des valeurs partitionDebut partitionFin, comme illustr en gras :
private void genererDonneesGraph(byte[] donnees, int partitionDebut, int partitionFin) { ... for (int x = partitionDebut; x < partitionFin; x ++) { ... }

5. Dans la fentre Code, ajoutez linstruction using suivante la liste en haut du fichier GraphWindow.xaml.cs :
using System.Threading.Tasks;

6. Dans la mthode tracerButton_Click, mettez en commentaire linstruction qui appelle la mthode genererDonneesGraph et ajoutez linstruction en gras qui cre un objet Task avec lobjet TaskFactory par dfaut et dmarre son excution :
... Stopwatch montre = Stopwatch.StartNew(); // genererDonneesGraph(donnees); Task premiere = Task.Factory.StartNew(() => genererDonneesGraph(donnees, 0, largeurPixel / 4)); ...

La tche excute le code spcifi par lexpression lambda. Les valeurs des paramtres partitionDebut et partitionFin indiquent que lobjet Task calcule les donnes de la premire moiti du graphique (les donnes du graphique complet se composent des points tracs pour les values comprises entre 0 et largeurPixel / 2). 7. Ajoutez une autre instruction qui cre et excute un deuxime objet Task sur un autre thread, comme cela est indiqu en gras :
... Task premiere = Task.Factory.StartNew(() => genererDonneesGraph(donnees, 0,

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches


largeurPixel / 4)); Task deuxieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 4, largeurPixel / 2)); ...

Cet objet Task invoque la mthode generateGraph et calcule les donnes des valeurs comprises entre largeurPixel / 4 et largeurPixel / 2. 8. Ajoutez linstruction suivante qui attend que les deux objets Task aient fini leur travail avant de continuer :
Task.WaitAll(premiere, deuxieme);

9. Dans le menu Dboguer, cliquez sur Dmarrer sans dbogage pour gnrer et excuter lapplication. 10. Affichez le Gestionnaire de tches de Windows, et cliquez sur longlet Performance sil nest pas affich. 11. Retournez dans la fentre Graph Demo, et cliquez sur Tracer graphique. Dans le Gestionnaire de tches de Windows, notez la valeur maximum de lutilisation du CPU lorsque le graphique est gnr. Lorsque le graphique apparat dans la fentre Graph Demo, enregistrez le temps mis pour gnrer le graphique. Rptez cette action plusieurs fois pour obtenir une valeur moyenne. 12. Fermez la fentre Graph Demo, et rduisez le Gestionnaire de tches de Windows. Cette fois-ci, vous devriez constater que lapplication sexcute beaucoup plus vite que prcdemment. Sur mon ordinateur, la dure a chut 2 682 millisecondes, ce qui constitue une rduction de prs de 40 %. En outre, vous devriez voir que lapplication utilise plus les curs du CPU. Sur une machine dual-core, lutilisation du CPU atteint 100 pour cent. Si vous avez un ordinateur quad-core, lutilisation du CPU ne sera pas aussi leve. Cela est d au fait que deux des curs ne seront pas occups. Pour rectifier cela et rduire encore le temps dexcution, ajoutez deux objets Task supplmentaires et divisez le travail en quatre parties dans la mthode tracerButton_Click, comme cela est indiqu ici en gras :
... Task premiere = Task.Factory.StartNew(() => genererDonneesGraph(donnees, 0, largeurPixel / 8)); Task deuxieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 8, largeurPixel / 4));

Partie 6 Cration de solutions professionnelles

Task troisieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 4, largeurPixel * 3 / 8)); Task quatrieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel * 3 / 8, largeurPixel / 2)); Task.WaitAll(premiere, deuxieme, troisieme, quatrieme); ...

Si vous navez quun processeur dual-core, vous pouvez quand mme tenter cette modification, et vous devriez quand mme noter un effet bnfique sur le temps dexcution. Cela est principalement d lefficacit de la TPL et des algorithmes du .NET Framework qui optimisent la manire dont les threads de chaque tche sont planifis.

Abstraction des tches avec la classe Parallel


En utilisant la classe Task, vous bnficiez dun contrle total sur le nombre de tches que votre application cre. Cependant, vous devez modifier la conception de lapplication pour adapter lutilisation des objets Task. Vous devez aussi ajouter du code pour synchroniser les oprations ; lapplication ne peut afficher le graphique que lorsque toutes les tches sont termines. Dans une application complexe, la synchronisation des tches peut devenir un processus problmatique dans lequel il est facile de commettre des erreurs. La classe Parallel de la TPL permet de parallliser certaines constructions de programmation courantes sans ncessiter la modification de la conception de lapplication. En interne, la classe Parallel cre son propre ensemble dobjets Task, et elle synchronise ces tches automatiquement quand elles sont termines. La classe Parallel est situe dans lespace de noms System.Threading.Tasks et fournit un jeu rduit de mthodes statiques que vous pouvez utiliser pour indiquer que le code doit tre excut en parallle si possible. Voici la liste de ces mthodes : Parallel.For Vous pouvez utiliser cette mthode la place dune commande C# for. Elle dfinit une boucle dans laquelle les itrations peuvent sexcuter en parallle en utilisant des tches. Cette mthode est lourdement surcharge (il y a neuf variantes), mais le principe gnral est identique pour chaque version ; on spcifie une valeur de dpart, une valeur de fin, et une rfrence une mthode qui accepte un paramtre integer. La mthode est excute pour chaque valeur comprise entre la valeur de dpart et la valeur de fin moins un, et le paramtre est rempli avec un entier qui spcifie la Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

valeur en cours. Lexemple suivant illustre une simple boucle for qui accomplit chaque itration en squence :
for (int x = 0; x < 100; x++) { // Accomplit le traitement de la boucle }

En fonction du traitement accompli par le corps de la boucle, vous pouvez remplacer cette boucle par une construction Parallel.For qui peut raliser des itrations en parallle, de la manire suivante :
Parallel.For(0, 100, performLoopProcessing); ... private void performLoopProcessing(int x) { // Accomplit le traitement de la boucle }

Les surchargements de la mthode Parallel.For permettent de fournir des donnes locales qui sont prives pour chaque thread, de spcifier diffrentes options pour crer les tches excutes par la mthode For, et de crer un objet ParallelLoopState qui peut tre utilis pour passer des informations dtat dautres itrations concurrentes de la boucle (lobjet ParallelLoopState est dcrit plus loin dans ce chapitre). Parallel.ForEach<T> Vous pouvez utiliser cette mthode la place dune commande C# foreach. Comme la mthode For, ForEach dfinit une boucle dans laquelle les itrations peuvent sexcuter en parallle. On spcifie une collection qui implmente linterface gnrique IEnumerable<T> et une rfrence une mthode qui accepte un seul paramtre de type T. La mthode est excute pour chaque lment de la collection, et llment est pass en paramtre la mthode. Les surchargements sont disponibles et permettent de fournir des donnes de thread local priv et de spcifier des options pour crer les tches excutes par la mthode ForEach. Parallel.Invoke Vous pouvez utiliser cette mthode pour excuter un ensemble dappels de mthodes sans paramtre sous la forme de tches parallles. On spcifie une liste dappels de mthodes dlgues (ou dexpressions lambda) qui nacceptent pas de paramtre et ne retourne pas de valeur. Chaque appel de mthode peut tre excut sur un thread spar, dans nimporte quel ordre. Par exemple, le code suivant effectue une srie dappels de mthodes :

Partie 6 Cration de solutions professionnelles

faireTravail(); faireAutreTravail(); faireEncoreAutreTravail();

Vous pouvez remplacer ces commandes par le code suivant qui invoque ces mthodes en utilisant une srie de tches :
Parallel.Invoke( faireTravail, faireAutreTravail, faireEncoreAutreTravail );

Vous devez garder lesprit que le .NET Framework dtermine le degr rel de paralllisme appropri lenvironnement et la charge de travail de lordinateur. Par exemple, si vous utilisez Parallel.For pour implmenter une boucle qui accomplit 1 000 itrations, le .NET Framework ne cre pas ncessairement 1 000 tches concurrentes ( moins que vous nayez un processeur particulirement puissant dot de mille curs). Au lieu de cela, le .NET Framework cre ce quil considre comme le nombre optimal de tches qui est un compromis entre les ressources disponibles et la ncessit de garder les processeurs actifs. Une seule tche peut accomplir plusieurs itrations, et les tches se coordonnent entre elles pour dterminer quelles itrations chaque tche va accomplir. Cela a pour consquence que vous ne pouvez pas garantir lordre dans lequel les itrations sont excutes, si bien que vous devez vous assurer quil ny a pas de dpendances entre les itrations ; dans le cas contraire, vous pouvez obtenir des rsultats inattendus, comme vous le verrez plus tard dans ce chapitre. Dans lexercice suivant, vous allez retourner la version originale de lapplication GraphData et utiliser la classe Parallel pour accomplir des oprations de manire concurrente.

Utilisation de la classe Parallel pour parallliser les oprations de lapplication GraphData


1. Dans Visual Studio 2010, ouvrez la solution GraphDemo, situe dans le dossier \Visual C Sharp Etape par tape\Chapitre 27\GraphDemo Utilisant La Classe Parallel de votre dossier Documents. Ceci est une copie de lapplication originale GraphDemo qui nutilise pas les tches pour linstant. Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches 2. Dans lExplorateur de solutions, dans le projet GraphDemo, dveloppez le nud GraphWindow.xaml, puis faites un double clic sur GraphWindow.xaml.cs pour afficher le code du formulaire dans la fentre Code. 3. Ajoutez linstruction using suivante la liste en haut du fichier :
using System.Threading.Tasks;

4. Recherchez la mthode genererDonneesGraph. Elle ressemble ceci :


private { int int int void genererDonneesGraph(byte[] donnees) a = largeurPixel / 2; b = a * a; c = hauteurPixel / 2;

for (int x = 0; x < a; x++) { int s = x * x; double p = Math.Sqrt(b - s); for (double i = -p; i < p; i += 3) { double r = Math.Sqrt(s + i * i) / a; double q = (r - 1) * Math.Sin(24 * r); double y = i / 3 + (q * c); tracerXY(donnees, (int)(-x + (largeurPixel / 2)), (int)(y + (hauteurPixel / 2))); tracerXY(donnees, (int)(x + (largeurPixel / 2)), (int)(y + (hauteurPixel / 2))); } } }

La boucle externe for qui parcourt les valeurs de la variable integer x est un parfait candidat pour la paralllisation. Vous pouvez aussi envisager la boucle interne base sur la variable i, mais cette boucle demande plus deffort pour la parallliser en raison du type de i (les mthodes de la classe Parallel attendent que la variable de contrle soit de type integer). En outre, si vous avez des boucles imbriques, comme cest le cas dans ce code, il est conseill de parallliser dabord les boucles externes, puis de voir si les performances de lapplication sont suffisantes. Si ce nest pas le cas, rabattez-vous sur les boucles imbriques et paralllisez-les en allant des boucles externes vers les boucles internes, et en testant les performances aprs chaque modification de boucle. Vous constaterez que dans de nombreux cas la paralllisation des boucles externes a le plus deffet sur les performances, alors que la

Partie 6 Cration de solutions professionnelles

modification des boucles internes a un effet marginal. 5. Dplacez le code du corps de la boucle for, et crez une nouvelle mthode prive void appele calculerDonnees avec ce code. La mthode calculerDonnees doit prendre un paramtre integer appel x et un tableau byte appel donnees. Dplacez aussi les instructions qui dclarent les variables locales a, b, et c de la mthode genererDonneesGraph au dbut de la mthode calculerDonnees. Le code suivant montre la mthode genererDonneesGraph avec le code supprim et la mthode calculerDonnees (ne compilez pas ce code pour le tester pour linstant) :
private void genererDonneesGraph(byte[] donnees) { for (int x = 0; x < a; x++) { } } private { int int int void calculerDonnees(int x, byte[] donnees) a = largeurPixel / 2; b = a * a; c = hauteurPixel / 2;

int s = x * x; double p = Math.Sqrt(b - s); for (double i = -p; i < p; i += 3) { double r = Math.Sqrt(s + i * i) / a; double q = (r - 1) * Math.Sin(24 * r); double y = i / 3 + (q * c); tracerXY(donnees, (int)(-x + (largeurPixel / 2)), (int)(y + (hauteurPixel / 2))); tracerXY(donnees, (int)(x + (largeurPixel / 2)), (int)(y + (hauteurPixel / 2))); } }

6. Dans la mthode genererDonneesGraph, modifiez la boucle for pour une instruction qui appelle la mthode statique Parallel.For, comme cela est indiqu en gras ici :
private void genererDonneesGraph(byte[] donnees) { Parallel.For (0, largeurPixel / 2, (int x) => { calculerDonnees(x, donnees); });

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches


}

Ce code est lquivalent parallle de la boucle originale for. Il parcourt les valeurs de 0 largeurPixel / 2 1 inclus. Chaque invocation sexcute en utilisant une tche (chaque tche peut excuter plus dune itration). La mthode Parallel.For ne se termine que lorsque toutes les tches quelle a cres ont achev leur travail. Rappelez-vous que la mthode Parallel.For sattend ce que le dernier paramtre soit une mthode qui naccepte quun seul paramtre integer. Elle appelle cette mthode en passant lindice de boucle en cours comme paramtre. Dans cet exemple, la mthode calculerDonnees ne correspond pas la signature ncessaire car elle prend deux paramtres : un integer et un tableau de types byte. Pour cette raison, le code utilise une expression lambda pour dfinir une mthode anonyme qui la signature approprie et qui agit comme un adaptateur qui appelle la mthode calculerDonnees avec les bons paramtres. 7. Dans le menu Dboguer, cliquez sur Dmarrer sans dbogage pour gnrer et excuter lapplication. 8. Affichez le Gestionnaire de tches de Windows, et cliquez sur longlet Performance sil nest pas dj affich. 9. Retournez dans la fentre Graph Demo, et cliquez sur Tracer graphique. Dans le Gestionnaire de tches de Windows, notez la valeur maximum dutilisation du CPU lorsque le graph est gnr. Quand le graphique apparat dans la fentre Graph Demo, enregistrez le temps mis pour gnrer le graphique. Rptez cette action plusieurs fois pour obtenir une valeur moyenne. 10. Fermez la fentre Graph Demo, et rduisez le Gestionnaire de tches de Windows. Vous devriez noter que lapplication sexcute une vitesse comparable la version prcdente qui utilisait des objets Task (et peut-tre un peu plus rapidement, en fonction du nombre de CPU dont vous disposez), et que lutilisation du CPU culmine 100 pourcent.

Quand faut-il ne pas utiliser la classe Parallel


Vous devez tre conscient quen dpit des apparences et du travail considrable accompli par lquipe de dveloppement de Visual Studio, la classe Parallel naccomplit pas des miracles ; vous ne pouvez pas lutiliser sans rflchir et vous ne devez pas vous attendre ce que vos applications sexcutent beaucoup plus vite

Partie 6 Cration de solutions professionnelles

et produisent les mmes rsultats. Le but de la classe Parallel est de parallliser les zones indpendantes de votre code qui sont lies au matriel. Les points importants du paragraphe prcdent sont lis au matriel et indpendant. Si votre code nest pas li au matriel, le fait de le parallliser namliorera pas ses performances. Lexercice suivant vous montre comment juger de lopportunit demployer la construction Parallel.Invoke pour raliser des appels de mthodes en parallle.

Opportunit dutilisation de Parallel.Invoke


1. Retournez dans Visual Studio 2010, et affichez le fichier GraphWindow.xaml.cs dans la fentre Code sil nest pas dj ouvert. 2. Examinez la mthode calculerDonnees. La boucle interne for contient les instructions suivantes :
tracerXY(donnees, (int)(-x + (largeurPixel / 2)), (int)(y + (largeurPixel / 2))); tracerXY(donnees, (int)(x + (largeurPixel / 2)), (int)(y + (largeurPixel / 2)));

Ces deux instructions dfinissent les octets du tableau donnees qui correspondent aux points spcifis par les deux paramtres passs la mthode. Rappelez-vous que les points du graphique se refltent autour de laxe X, si bien que la mthode tracerXY est appele pour la valeur positive de X, mais aussi pour sa valeur ngative. Ces deux instructions semblent de bons candidats pour la paralllisation car elles dfinissent des octets diffrents dans le tableau donnees et leur ordre dexcution na pas dimportance. 3. Modifiez ces deux instructions et encapsulez-les dans un appel de mthode Parallel.Invoke, comme cela est indiqu ci-dessous. Vous noterez que les deux appels sont prsent encapsuls dans des expressions lambda, et que le point-virgule la fin du premier appel tracerXY est remplac par une virgule, et que le point-virgule la fin du deuxime appel tracerXY a t supprim car ces instructions sont maintenant une liste de paramtres :
Parallel.Invoke( () => tracerXY(donnees, (int)(-x + (largeurPixel / 2)), (int)(y + (largeurPixel / 2))),

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches


() => tracerXY(donnees, (int)(x + (largeurPixel / 2)), (int)(y + (largeurPixel / 2))) );

4. Dans le menu Dboguer, cliquez sur Dmarrer sans dbogage pour gnrer et excuter lapplication. 5. Dans la fentre Graph Demo, cliquez sur Tracer graphique. Enregistrez le temps mis pour gnrer le graphique. Rptez cette action plusieurs fois pour obtenir une valeur moyenne. De manire assez inattendue, vous devriez trouver que lapplication est beaucoup plus longue sexcuter. Elle peut tre jusqu 20 fois plus lente que prcdemment. 6. Fermez la fentre Graph Demo. Vous devez sans doute vous poser la question de savoir ce qui se passe. Pourquoi lapplication est-elle tellement ralentie ? La rponse se trouve dans la mthode tracerXY. Si vous examinez nouveau cette mthode, vous verrez que cest trs simple :
private void tracerXY(byte[] donnees, int x, int y) { donnees[x + y * largeurPixel] = 0xFF; }

Il y a trs peu de chose dans cette mthode qui met du temps sexcuter, et il ne sagit absolument pas de code li au matriel. En fait, le code est tellement simple que la surcharge de cration dune tche, lexcution de cette tche sur un thread spar, et lattente de la fin de cette tche sont bien plus importantes que le cot de lexcution de cette mthode directement. La surcharge supplmentaire ne peut coter que quelques millisecondes chaque appel de mthode, mais vous devez garder lesprit que cette mthode est excute un trs grand nombre de fois ; lappel de mthode est situ dans une boucle imbrique et il est excut des milliers de fois, si bien que toutes ces petites surcharges finissent par faire une somme. En rgle gnrale, il faut utiliser Parallel.Invoke seulement quand cela en vaut la peine et rserver Parallel.Invoke pour les oprations qui sollicitent le matriel de manire intensive. Comme cela est mentionn plus haut dans ce chapitre, lautre point important dans lutilisation de la classe Parallel est que les oprations doivent tre indpendantes. Par exemple, si vous tentez dutiliser Parallel.For pour parallliser

Partie 6 Cration de solutions professionnelles

une boucle dans laquelle les itrations ne sont pas indpendantes, les rsultats seront imprvisibles. Pour voir ce que cela signifie, examinez le programme suivant :
using System; using System.Threading; using System.Threading.Tasks; namespace BoucleParallele { class Program { private static int accumulateur = 0; static void Main(string[] args) { for (int i = 0; i < 100; i++) { AjouterAAccumulateur(i); } Console.WriteLine("Accumulateur est {0}", accumulateur); } private static void AjouterAAccumulateur(int donnees) { if ((accumulateur % 2) == 0) { accumulateur += donnees; } else { accumulateur -= donnees; } }

Ce programme parcourt les valeurs de 0 99 et appelle la mthode AjouterAAccumulateur avec chacune de ces valeurs. La mthode AjouterAAccumulateur examine la valeur en cours de la variable accumulateur, et si elle est paire elle ajoute la valeur du paramtre la variable accumulateur ; dans le cas contraire, elle soustrait la valeur du paramtre. la fin du programme, le rsultat est affich. Vous trouverez cette application dans la solution BoucleParallele, situe dans le dossier \Visual C Sharp Etape par tape\Chapitre 27\BoucleParallele de votre dossier Documents. Si vous excutez ce programme, il Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

doit afficher une valeur de 100. Pour augmenter le degr de paralllisme de cette application simple, vous pouvez tre tent de remplacer la boucle for de la mthode Main par Parallel.For :
static void Main(string[] args) { Parallel.For (0, 100, AjouterAAccumulateur); Console.WriteLine("Accumulateur est {0}", accumulateur); }

Il ny a cependant aucune garantie que les tches cres pour excuter les diffrentes invocations de la mthode AjouterAAccumulateur sexcuteront dans une squence spcifique (ce code nest pas non plus thread-safe car plusieurs threads excutant les tches peuvent tenter de modifier la variable accumulateur en mme temps). La valeur calcule par la mthode AjouterAAccumulateur dpend du maintien de la squence, si bien que le rsultat de la modification est que lapplication peut prsent gnrer des valeurs diffrentes chaque fois quelle sexcute. Dans cet exemple simple, vous ne verrez peut-tre aucune diffrence dans le calcul de la valeur car la mthode AjouterAAccumulateur sexcute trs rapidement et le .NET Framework peut choisir dexcuter chaque invocation squentiellement en utilisant le mme thread. Cependant, si vous effectuez le changement suivant (indiqu en gras) dans la mthode AjouterAAccumulateur, vous obtiendrez des rsultats diffrents :
private static void AjouterAAccumulateur(int donnees) { if ((accumulateur % 2) == 0) { accumulateur += donnees; Thread.Sleep(10); // attente de 10 millisecondes } else { accumulateur -= donnees; } }

La mthode Thread.Sleep provoque simplement larrt du thread en cours pendant la dure spcifie. Cette modification stimule le thread, en accomplissant un traitement supplmentaire qui affecte la manire dont le .NET Framework planifie les tches, qui sexcutent maintenant sur des threads diffrents, ce qui provoque une squence diffrente.

Partie 6 Cration de solutions professionnelles

En rgle gnrale, il faut utiliser Parallel.For et Parallel.ForEach seulement quand vous pouvez garantir que chaque itration de la boucle est indpendante, et vous devez tester votre code minutieusement. Ces considrations sappliquent galement Parallel.Invoke ; utilisez cette construction pour effectuer des appels de mthodes seulement si elles sont indpendantes et si lapplication ne dpend pas de leur excution dans un certain ordre.

Retour dune valeur partir dune tche


Jusqu prsent, tous les exemples que nous avons tudis utilisaient un objet Task pour excuter le code qui accomplit une partie du travail, mais ne retourne pas de valeur. Vous pouvez cependant avoir envie dexcuter une mthode qui calcule un rsultat. La TPL inclut une variante gnrique de la classe Task, Task<TResult>, que vous pouvez utiliser cet effet. On cre et excute un objet Task<TResult> de la mme manire quun objet Task. La principale diffrence est que la mthode excute par lobjet Task<TResult> retourne une valeur et que lon spcifie le type de cette valeur de retour, en tant que paramtre type, T, de lobjet Task. Par exemple, la mthode calculerValeur illustre dans le code suivant retourne une valeur integer. Pour invoquer cette mthode en utilisant une tche, on cre un objet Task<int> et on appelle ensuite la mthode Start. On obtient la valeur retourne par la mthode en interrogeant la proprit Result de lobjet Task<int>. Si la tche na pas termin lexcution de la mthode et que le rsultat nest pas encore disponible, la proprit Result bloque lappelant. Cela signifie que vous navez pas accomplir vous-mme de synchronisation, et que vous savez que lorsque la proprit Result retourne une valeur la tche a termin son travail.
Task<int> tacheCalculerValeur = new Task<int>(() => calculerValeur(...)); tacheCalculerValeur.Start(); // Invocation de la mthode calculerValeur ... int donneesCalculees = tacheCalculerValeur.Result; // On bloque jusqu ce que tacheCalculerValeur soit termine ... private int calculerValeur(...) { int uneValeur; // Calcul et remplissage de uneValeur ... return uneValeur;

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches


}

Bien entendu, vous pouvez aussi utiliser la mthode StartNew dun objet TaskFactory pour crer un objet Task<TResult> et dmarrer son excution. Lexemple de code suivant montre comment utiliser lobjet par dfaut TaskFactory dun objet Task<int> pour crer et excuter une tche qui invoque la mthode calculerValeur :
Task<int> tacheCalculerValeur = Task<int>.Factory.StartNew(() => calculerValeur(...)); ...

Pour simplifier un peu votre code (et prendre en charge les tches qui retournent des types anonymes), la classe TaskFactory fournit des surcharges gnriques de la mthode StartNew et peut dduite le type retourn par la mthode excute par une tche. En outre, la classe Task<TResult> hrite de la classe Task. Cela signifie que vous pouvez rcrire lexemple prcdent de la manire suivante :
Task tacheCalculerValeur = Task.Factory.StartNew(() => calculerValeur(...)); ...

Le prochain exercice offre un exemple plus dtaill : vous allez restructurer lapplication GraphDemo afin dutiliser un objet Task<TResult>. Bien que cet exercice semble un peu scolaire, la technique qui est ici illustre pourra se rvler utile dans de nombreuses situations relles.

Modification de lapplication GraphDemo afin dutiliser un objet Task<TResult>


1. Dans Visual Studio 2010, ouvrez la solution GraphDemo, situ dans le dossier \Visual C Sharp Etape par tape\Chapitre 27\GraphDemo Utilisant Taches Qui Retournent Rsultats de votre dossier Documents. Il sagit dune copie de lapplication GraphDemo qui cre un ensemble de quatre tches que vous avez tudie dans un exercice prcdent. 2. Dans lExplorateur de solutions, dans le projet GraphDemo, dveloppez le nud GraphWindow.xaml, puis faites un double clic sur GraphWindow.xaml.cs pour afficher le code du formulaire dans la fentre Code. 3. Recherchez la mthode tracerButton_Click. Cest la mthode qui sexcute lorsque lutilisateur clique sur le bouton Tracer graphique du formulaire.

Partie 6 Cration de solutions professionnelles

Actuellement, elle cre un ensemble dobjets Task pour accomplir les diffrents calculs ncessaires et gnrer les donnes du graphique, et elle attend que les objets Task se terminent avant dafficher les rsultats dans le contrle Image du formulaire. 4. En dessous de la mthode tracerButton_Click, ajoutez une nouvelle mthode appele obtenirDonneesPourGraph. Cette mthode doit prendre un paramtre integer appel tailleDonnees et retourne un tableau byte, comme cela est illustr dans le code suivant :
private byte[] obtenirDonneesPourGraph(int tailleDonnees) { }

Vous allez ajouter du code cette mthode pour gnrer les donnes du graphique dans un tableau byte et retourner ce tableau lappelant. Le paramtre tailleDonnees spcifie la taille du tableau. 5. Dplacez linstruction qui cre le tableau donnees de la mthode tracerButton_Click dans la mthode obtenirDonneesPourGraph comme cela est indiqu en gras :
private byte[] obtenirDonneesPourGraph(int tailleDonnees) { byte[] donnees = new byte[tailleDonnees]; }

6. Dplacez le code qui cre, excute et attend que les objets Task remplissent le tableau donnees de la mthode tracerButton_Click dans la mthode obtenirDonneesPourGraph, et ajoutez une instruction return la fin de la mthode qui retourne le tableau donnees lappelant. Le code complet de la mthode obtenirDonneesPourGraph doit ressembler ceci :
private byte[] obtenirDonneesPourGraph(int tailleDonnees) { byte[] donnees = new byte[tailleDonnees]; Task premiere = Task.Factory.StartNew(() => genererDonneesGraph(donnees, 0, largeurPixel / 8)); Task deuxieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 8, largeurPixel / 4)); Task troisieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 4, largeurPixel * 3 / 8)); Task quatrieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel * 3 / 8, largeurPixel / 2)); Task.WaitAll(premiere, deuxieme, troisieme, quatrieme);

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches


return donnees;

Astuce Vous pouvez remplacer le code qui cre les tches et attend leur achvement par la construction Parallel.Invoke suivante :
Parallel.Invoke( () => Task.Factory.StartNew(() => genererDonneesGraph(donnees, 0, largeurPixel / 8)) () => Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 8, largeurPixel / 4)), () => Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 4, largeurPixel * 3 / 8)), () => Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel * 3 / 8, largeurPixel / 2)) );

7. Dans la mthode tracerButton_Click, aprs linstruction qui cre la variable Stopwatch utilise pour chronomtrer les tches, ajoutez la commande indique en gras qui cre un objet Task<byte[]> appel obtenirDonneesTache et utilise cet objet pour excuter la mthode obtenirDonneesPourGraph. Cette mthode retourne un tableau byte, si bien que le type de la tche est Task<byte []>. Lappel de la mthode StartNew rfrence une expression lambda qui invoque la mthode obtenirDonneesPourGraph et passe la variable tailleDonnees en paramtre cette mthode.
private void tracerButton_Click(objet sender, RoutedEventArgs e) { ... Stopwatch montre = Stopwatch.StartNew(); Task<byte[]> obtenirDonneesTache = Task<byte[]>.Factory.StartNew(() => obtenirDonneesPourGraph(tailleDonnees)); ... }

8. Aprs la cration et le dmarrage de lobjet Task<byte []>, ajoutez linstruction illustre en gras qui examine la proprit Result afin de rcuprer le tableau donnees retourn par la mthode obtenirDonneesPourGraph dans une variable locale de tableau byte appele donnees. Rappelez-vous que la proprit Result bloque lappelant tant que la tche nest pas termine, si bien que vous navez pas besoin dattendre la fin de la tche.
private void tracerButton_Click(objet sender, RoutedEventArgs e) {

Partie 6 Cration de solutions professionnelles

... Task<byte[]> obtenirDonneesTache = Task<byte[]>.Factory.StartNew(() => obtenirDonneesPourGraph(tailleDonnees)); byte[] donnees = obtenirDonneesTache.Result; ... }

Note Il peut sembler un peu trange de crer une tche, puis dattendre immdiatement son achvement avant de faire quelque chose dautre car cela ne fait que rajouter de la surcharge lapplication. Vous verrez cependant dans la prochaine section pourquoi cette approche a t adopte.

9. Vrifiez que le code complet de la mthode tracerButton_Click ressemble ceci :


private void tracerButton_Click(objet sender, RoutedEventArgs e) { if (graphBitmap == null) { graphBitmap = new WriteableBitmap(largeurPixel, hauteurPixel, dpiX, dpiY, PixelFormats.Gray8, null); } int octetsParPixel = (graphBitmap.Format.BitsPerPixel + 7) / 8; int stride = octetsParPixel * largeurPixel; int tailleDonnees = stride * hauteurPixel; Stopwatch montre = Stopwatch.StartNew(); Task<byte[]> obtenirDonneesTache = Task<byte[]>.Factory.StartNew(() => obtenirDonneesPourGraph(tailleDonnees)); byte[] donnees = obtenirDonneesTache.Result; duree.Content = string.Format("Dure (ms) : {0}", montre.ElapsedMilliseconds); graphBitmap.WritePixels(new Int32Rect(0, 0, largeurPixel, hauteurPixel), donnees, stride, 0); graphImage.Source = graphBitmap; }

10. Dans le menu Dboguer, cliquez sur Dmarrer sans dbogage pour gnrer et excuter lapplication. 11. Dans la fentre Graph Demo, cliquez sur Tracer graphique. Vrifiez que le graphique est gnr comme auparavant et que le temps daffichage est similaire aux valeurs prcdentes (le temps affich peut tre un peu plus long Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

car le tableau donnees est prsent cr par la tche, alors quauparavant il tait cr avant que la tche ne dmarre son excution). 12. Fermez la fentre Graph Demo.

Utilisation conjointe des tches et des threads dinterface utilisateur


La section Pourquoi grer le mode multitche par un traitement parallle ? , au dbut de ce chapitre, a mis en lumire les deux principales raisons de lutilisation du multitche dans une application : lamlioration du dbit et laugmentation de la ractivit. La TPL est certainement dun grand secours dans lamlioration du dbit, mais vous devez tre conscient que lutilisation de la seule TPL nest pas lultime solution pour augmenter la ractivit, particulirement dans une application qui offre une interface utilisateur graphique. Dans lapplication GraphDemo utilise comme base des exercices de ce chapitre, bien que le temps mis pour gnrer les donnes du graphique soit rduit par lutilisation des tches, lapplication montre elle-mme les symptmes classiques de nombreuses interfaces utilisateur graphiques qui accomplissent des calculs sollicitant le processeur : elle ne ragit pas aux sollicitations de lutilisateur lors des calculs. Par exemple, si vous excutez lapplication GraphDemo de lexercice prcdent, cliquez sur le bouton Tracer graphique, puis tentez de dplacer la fentre Graph Demo en cliquant sur la barre de titre ; vous constaterez quelle ne bouge pas tant que les diffrentes tches utilises pour gnrer le graphique ne sont pas termines et que le graphique nest pas affich. Dans une application professionnelle, vous devez vous assurer que lon peut toujours utiliser votre application mme si certaines parties sont occupes accomplir dautres tches. Cest ce moment-l que vous devez utiliser des threads ainsi que des tches. Au chapitre 23, vous avez vu comment les lments qui constituent une interface utilisateur graphique dune application WPF sexcutent tous dans le thread dinterface utilisateur. Cela permet de garantir la cohrence et la scurit, et cela empche deux threads de corrompre les structures de donnes internes utilises par WPF pour afficher linterface utilisateur. Rappelez-vous que lon peut aussi utiliser lobjet WPF Dispatcher pour mettre en file dattente les demandes du

Partie 6 Cration de solutions professionnelles

thread dinterface utilisateur, et que ces demandes peuvent mettre jour linterface utilisateur. Le prochain exercice revient sur lobjet Dispatcher et montre comment lutiliser pour implmenter une solution ractive conjointement avec des tches qui garantissent le meilleur dbit disponible.

Amlioration de la ractivit de lapplication GraphDemo


1. Retournez dans Visual Studio 2010, et affichez le fichier GraphWindow.xaml.cs dans la fentre Code si elle nest pas dj ouverte. 2. Ajoutez une nouvelle mthode appele faireFonctionnerTracerButton audessous de la mthode tracerButton_Click. Cette mthode ne doit accepter aucun paramtre et ne doit pas retourner de valeur. Dans les tapes suivantes, vous allez dplacer le code qui cre et excute les tches qui gnrent les donnes du graphique dans cette mthode, et vous allez excuter cette mthode dans un thread spar, ce qui laissera le thread dinterface utilisateur libre de grer la saisie utilisateur.
private void faireFonctionnerTracerButton() { }

3. Dplacez tout le code, except linstruction if qui cre lobjet graphBitmap de la mthode tracerButton_Click dans la mthode faireFonctionnerTracerButton. Notez que certaines de ces instructions tentent daccder aux lments de linterface utilisateur ; vous allez modifier ces commandes pour utiliser lobjet Dispatcher plus tard dans cet exercice. Les mthodes tracerButton_Click et faireFonctionnerTracerButton doivent ressembler ceci :
private void tracerButton_Click(objet sender, RoutedEventArgs e) { if (graphBitmap == null) { graphBitmap = new WriteableBitmap(largeurPixel, hauteurPixel, dpiX, dpiY, PixelFormats.Gray8, null); } } private { int int int void faireFonctionnerTracerButton() octetsParPixel = (graphBitmap.Format.BitsPerPixel + 7) / 8; stride = octetsParPixel * largeurPixel; tailleDonnees = stride * hauteurPixel;

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

Stopwatch montre = Stopwatch.StartNew(); Task<byte[]> obtenirDonneesTache = Task<byte[]>.Factory.StartNew(() => obtenirDonneesPourGraph(tailleDonnees)); byte[] donnees = obtenirDonneesTache.Result; duree.Content = string.Format("Dure (ms) : {0}", montre.ElapsedMilliseconds); graphBitmap.WritePixels(new Int32Rect(0, 0, largeurPixel, hauteurPixel), donnees, stride, 0); graphImage.Source = graphBitmap; }

4. Dans la mthode tracerButton_Click, aprs le bloc if, crez un dlgu Action appel actionFaireFonctionerTracerButton qui rfrence la mthode faireFonctionnerTracerButton, comme cela est indiqu ici en gras :
private void tracerButton_Click(objet sender, RoutedEventArgs e) { ... Action actionFaireFonctionerTracerButton = new Action(faireFonctionnerTracerButton); }

5. Appelez la mthode BeginInvoke sur le dlgu actionFaireFonctionerTracerButton. La mthode BeginInvoke du type Action excute la mthode associe au dlgu (en loccurrence, la mthode faireFonctionnerTracerButton) sur un nouveau thread.

Note Le type Action fournit aussi la mthode Invoke, qui excute la mthode dlgue sur le thread en cours. Ce nest pas le comportement que nous souhaitons dans ce cas parce que cela bloque linterface utilisateur et cela empche toute ractivit lors de lexcution de la mthode.

La mthode BeginInvoke accepte des paramtres que vous pouvez utiliser pour mettre en place une notification quand la mthode se termine, ainsi que des donnes que lon peut passer la mthode dlgue. Dans cet exemple, on na pas besoin dtre notifi quand la mthode se termine et la mthode naccepte pas de paramtre, si bien que lon spcifie une valeur null pour les paramtres, comme cela est indiqu en gras :
private void tracerButton_Click(objet sender, RoutedEventArgs e) { ...

Partie 6 Cration de solutions professionnelles

Action actionFaireFonctionerTracerButton = new Action(faireFonctionnerTracerButton); actionFaireFonctionerTracerButton.BeginInvoke(null, null); }

Le code va se compiler, mais si vous tentez de lexcuter, il ne fonctionnera pas correctement quand vous cliquerez sur le bouton Tracer graphique. Cela est d au fait que plusieurs commandes de la mthode faireFonctionnerTracerButton tentent daccder des lments de linterface utilisateur, et cette mthode ne sexcute pas sur le thread dinterface utilisateur. On a dj rencontr ce problme au chapitre 23, et on a tudi la solution (utilisation de lobjet Dispatcher pour le thread dinterface utilisateur afin daccder aux lments de linterface utilisateur). Les tapes suivantes modifient ces instructions afin dutiliser lobjet Dispatcher pour accder aux lments de linterface utilisateur partir du bon thread. 6. Ajoutez linstruction using suivante la liste au sommet du fichier :
using System.Windows.Threading;

Lnumration DispatcherPriority rside dans cet espace de noms. Vous allez utiliser cette numration quand vous planifiez le code pour quil sexcute sur le thread dinterface utilisateur grce lobjet Dispatcher. 7. Au dbut de la mthode faireFonctionnerTracerButton, examinez linstruction qui initialise la variable octetsParPixel :
private void faireFonctionnerTracerButton() { int octetsParPixel = (graphBitmap.Format.BitsPerPixel + 7) / 8; ... }

Cette instruction rfrence lobjet graphBitmap qui appartient au thread dinterface utilisateur. Vous pouvez accder cet objet seulement partir du code qui sexcute sur le thread dinterface utilisateur. Changez cette instruction pour initialiser la variable octetsParPixel zro, et ajoutez une commande pour appeler la mthode Invoke de lobjet Dispatcher, comme cela est indiqu en gras ici :
private void faireFonctionnerTracerButton() { int octetsParPixel = 0; tracerButton.Dispatcher.Invoke(new Action(() =>

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches


{ octetsParPixel = (graphBitmap.Format.BitsPerPixel + 7) / 8; }), DispatcherPriority.ApplicationIdle);

...

Si vous vous rappelez le chapitre 23, vous pouvez accder lobjet Dispatcher via la proprit Dispatcher de nimporte quel lment de linterface utilisateur. Ce code utilise le bouton tracerButton. La mthode Invoke attend un dlgu et ventuellement une priorit de dispatcher. Dans ce cas, le dlgu rfrence une expression lambda. Le code de cette expression sexcute sur le thread dinterface utilisateur. Le paramtre DispatcherPriority indique que cette instruction ne doit sexcuter que lorsque lapplication est au repos et il ny a rien dautre de plus important dans linterface utilisateur (comme un clic sur un bouton, la saisie dun texte, ou le dplacement dune fentre). 8. Examinez les trois dernires instructions de la mthode faireFonctionnerTracerButton mthode :
private void faireFonctionnerTracerButton() { ... duree.Content = string.Format("Duration (ms): {0}", montre.ElapsedMilliseconds); graphBitmap.WritePixels(new Int32Rect(0, 0, largeurPixel, hauteurPixel), donnees, stride, 0); graphImage.Source = graphBitmap; }

Ces instructions rfrencent les objets duree, graphBitmap et graphImage, qui font tous partie de linterface utilisateur. En consquence, vous devez modifier ces instructions pour quelles sexcutent sur le thread dinterface utilisateur. 9. Modifiez ces instructions et excutez-les en utilisant la mthode Dispatcher.Invoke, comme cela est indiqu en gras ici :
private void faireFonctionnerTracerButton() { ... tracerButton.Dispatcher.Invoke(new Action(() => { duree.Content = string.Format("Dure (ms) : {0}", montre.ElapsedMilliseconds); graphBitmap.WritePixels(new Int32Rect(0, 0, largeurPixel, hauteurPixel), donnees, stride, 0);

Partie 6 Cration de solutions professionnelles

graphImage.Source = graphBitmap; }), DispatcherPriority.ApplicationIdle);

Ce code convertit ces instructions en une expression lambda encapsule dans un dlgu Action, puis il invoque ce dlgu grce lobjet Dispatcher. 10. Dans le menu Dboguer, cliquez sur Dmarrer sans dbogage pour gnrer et excuter lapplication. 11. Dans la fentre Graph Demo, cliquez sur Tracer graphique et avant que le graphique napparaisse, dplacez rapidement la fentre sur un autre emplacement de lcran. Vous devriez constater que la fentre rpond immdiatement et que vous navez pas attendre laffichage du graphique. 12. Fermez la fentre Graph Demo.

Annulation des tches et gestion des exceptions


Parmi les autres exigences courantes des applications qui accomplissent des oprations longues, on compte la facult darrter ces oprations si ncessaire. Cependant, vous ne devez pas annuler une tche comme cela, car cela peut corrompre les donnes de lapplication. Dans ces conditions, la TPL implmente une stratgie dannulation cooprative. Lannulation cooprative permet une tche de slectionner le moment opportun pendant lequel un traitement peut tre arrt et permet galement de dfaire tout travail qui a t accompli avant lannulation si ncessaire.

Mcanisme dannulation cooprative


Lannulation cooprative est base sur la notion de jeton dannulation. Un jeton dannulation est une structure qui reprsente une demande dannulation dune ou plusieurs tches. La mthode quune tche excute doit inclure un paramtre System.Threading.CancellationToken. Une application qui veut annuler une tche dfinit la proprit Boolean IsCancellationRequested de ce paramtre true. La mthode qui excute la tche peut interroger cette proprit diffrents moments au cours de son traitement. Si cette proprit est initialise set un certain moment, elle sait que lapplication a demand que la tche soit annule. De plus, Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

la mthode sait quel travail a t effectu jusque-l, si bien quelle peut annuler toutes les modifications si ncessaire avant de se terminer. Sinon, la mthode peut simplement ignorer la demande et continuer son excution si elle ne souhaite pas annuler la tche.

Astuce Vous devez interroger le jeton dannulation dune tche frquemment, mais pas au point daffecter les performances de la tche. Si possible, vous devez tendre vrifier lannulation au moins toutes les 10 millisecondes, mais pas plus frquemment que toutes les millisecondes.

Une application obtient un jeton dannulation CancellationToken en crant un objet System.Threading.CancellationTokenSource et en interrogeant la proprit Token de cet objet. Lapplication peut ensuite passer cet objet CancellationToken comme paramtre nimporte quelle mthode dmarre par les tches cres et excutes par une application. Si lapplication a besoin dannuler les tches, elle appelle la mthode Cancel de lobjet CancellationTokenSource. Cette mthode dfinit la proprit IsCancellationRequested de lobjet CancellationToken pass toutes les tches. Lexemple de code suivant montre comment crer un jeton dannulation et lutiliser pour annuler une tche. La mthode commencerTaches instancie la variable annulationSourceJeton et obtient une rfrence lobjet CancellationToken qui est disponible via cette variable. Le code cre et excute ensuite une tche qui excute la mthode faireTravail. Plus tard, le code appelle la mthode Cancel de la source du jeton dannulation, qui initialise le jeton dannulation. La mthode faireTravail interroge la proprit IsCancellationRequested du jeton dannulation. Si la proprit est dfinie, la mthode se termine ; dans le cas contraire, elle poursuit son excution.
public class MonApplication { ... // Mthode qui cre et gre une tche private void commencerTaches() { // Cre la source du jeton dannulation et obtient un jeton dannulation CancellationTokenSource annulationSourceJeton = new CancellationTokenSource(); CancellationToken annulationJeton = annulationJeton.Token;

Partie 6 Cration de solutions professionnelles

// Cre une tche et dmarre lexcution de la mthode faireTravail Task maTache = Task.Factory.StartNew(() => faireTravail(annulationJeton)); ... if (...) { // Annulation de la tche annulationSourceJeton.Cancel(); } ...

// Mthode excute par la tche private void faireTravail(CancellationToken jeton) { ... // Si lapplication a dfini le jeton dannulation, on termine le traitement if (jeton.IsCancellationRequested) { // On range tout et on termine ... return; } // Si la tche na pas t annule, on continue lexcution normalement ... } }

Outre le fait quelle fournit un bon contrle sur le traitement de lannulation, cette approche est volutive et peut sadapter nimporte quel nombre de tches. Vous pouvez dmarrer plusieurs tches et passer le mme objet CancellationToken chacune dentre elles. Si vous appelez Cancel sur lobjet CancellationTokenSource, chaque tche verra que la proprit IsCancellationRequested a t initialise et peut ainsi ragir en consquence. Vous pouvez aussi enregistrer une mthode callback avec le jeton dannulation en utilisant la mthode Register. Quand une application invoque la mthode Cancel de lobjet correspondant CancellationTokenSource, ce callback sexcute. Vous ne pouvez cependant pas garantir le moment dexcution de cette mthode ; ce peut tre avant ou aprs que les tches ont accompli leur propre traitement dannulation, ou mme au cours de ce traitement.
... annulationJeton.Register(faireTravailSupplementaire); ...

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches


private void faireTravailSupplementaire() { // Accomplit un traitement supplmentaire dannulation }

Dans lexercice suivant, vous allez ajouter une fonctionnalit dannulation lapplication GraphDemo.

Ajout dune fonctionnalit dannulation lapplication GraphDemo


1. Dans Visual Studio 2010, ouvrez la solution GraphDemo, situe dans le dossier \Visual C Sharp Etape par tape\Chapitre 27\GraphDemo Annulant Taches de votre dossier Documents. Il sagit dune copie de lapplication GraphDemo de lexercice prcdent qui utilise des tches et des threads pour amliorer la ractivit. 2. Dans lExplorateur de solutions, dans le projet GraphDemo, faites un double clic sur GraphWindow.xaml pour afficher le formulaire dans la fentre Design. 3. partir de la Bote outils, ajoutez un contrle Button au formulaire sous le label duree. Alignez le bouton horizontalement avec le bouton tracerButton. Dans la fentre Proprits, modifiez la proprit Name du nouveau bouton annulerButton, et changez la proprit Content Annuler. Le formulaire modifi doit ressembler limage illustre ci-dessous :

4. Faites un double clic sur le bouton Annuler afin de crer une mthode de gestionnaire dvnement Click appele annulerButton_Click. 5. Dans le fichier GraphWindow.xaml.cs, localisez la mthode obtenirDonneesPourGraph. Cette mthode cre les tches utilises par lapplication et attend quelles se terminent. Dplacez la dclaration des

Partie 6 Cration de solutions professionnelles

variables Task au niveau de la classe GraphWindow comme cela est illustr en gras dans le code suivant, puis modifiez la mthode obtenirDonneesPourGraph pour instancier ces variables :
public partial class GraphWindow : Window { ... private Task premiere, deuxieme, troisieme, quatrieme; ... private byte[] obtenirDonneesPourGraph(int tailleDonnees) { byte[] donnees = new byte[tailleDonnees]; premiere = Task.Factory.StartNew(() => genererDonneesGraph(donnees, 0, largeurPixel / 8)); deuxieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 8, largeurPixel / 4)); troisieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 4, largeurPixel * 3 / 8)); quatrieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel * 3 / 8, largeurPixel / 2)); Task.WaitAll(premiere, deuxieme, troisieme, quatrieme); return donnees; } }

6. Ajoutez la dclaration using suivante la liste au sommet du fichier :


using System.Threading;

Les types utiliss par lannulation cooprative rsident dans cet espace de noms. 7. Ajoutez un membre CancellationTokenSource appel sourceJeton la classe GraphWindow, et initialisez-le null, comme cela est indiqu en gras :
public class GraphWindow : Window { ... private Task premiere, deuxieme, troisieme, quatrieme; private CancellationTokenSource sourceJeton = null; ... }

8. Trouvez la mthode genererDonneesGraph, et ajoutez un paramtre CancellationToken appel jeton la dfinition de la mthode :
private void genererDonneesGraph(byte[] donnees, int partitionDebut, int partitionFin, CancellationToken jeton)

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches


{ }

...

9. Dans la mthode genererDonneesGraph, au dbut de la boucle interne for, ajoutez le code suivant illustr en gras pour vrifier si lannulation a t demande. Si tel est le cas, quittez la mthode ; dans le cas contraire, continuez calculer les valeurs et tracer le graphique.
private void genererDonneesGraph(byte[] donnees, int partitionDebut, int partitionFin, CancellationToken jeton) { int a = largeurPixel / 2; int b = a * a; int c = hauteurPixel / 2; for (int x = partitionDebut; x < partitionFin; x ++) { int s = x * x; double p = Math.Sqrt(b - s); for (double i = -p; i < p; i += 3) { if (jeton.IsCancellationRequested) { return; } double r = Math.Sqrt(s + i * i) / a; double q = (r - 1) * Math.Sin(24 * r); double y = i / 3 + (q * c); tracerXY(donnees, (int)(-x + (largeurPixel / 2)), (int)(y + (hauteurPixel / 2))); tracerXY(donnees, (int)(x + (largeurPixel / 2)), (int)(y + (hauteurPixel / 2))); } } }

10. Dans la mthode obtenirDonneesPourGraph, ajoutez les instructions suivantes illustres en gras qui instancient la variable sourceJeton et rcuprent lobjet CancellationToken dans une variable appele jeton :
private byte[] obtenirDonneesPourGraph(int tailleDonnees) { byte[] donnees = new byte[tailleDonnees]; sourceJeton = new CancellationTokenSource(); CancellationToken jeton = sourceJeton.Token; ... }

Partie 6 Cration de solutions professionnelles

11. Modifiez les instructions qui crent et excutent les quatre tches, et passent la variable jeton comme dernier paramtre la mthode genererDonneesGraph :
premiere = Task.Factory.StartNew(() => genererDonneesGraph(donnees, 0, largeurPixel / 8, jeton)); deuxieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 8, largeurPixel / 4, jeton)); troisieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 4, largeurPixel * 3 / 8, jeton)); quatrieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel * 3 / 8, largeurPixel / 2, jeton));

12. Dans la mthode annulerButton_Click, ajoutez le code indiqu ici en gras :


private void annulerButton_Click(objet sender, RoutedEventArgs e) { if (sourceJeton != null) { sourceJeton.Cancel(); } }

Ce code vrifie que la variable sourceJeton a t instancie ; si cest le cas, le code invoque la mthode Cancel sur cette variable. 13. Dans le menu Dboguer, cliquez sur Dmarrer sans dbogage pour crer et excuter lapplication. 14. Dans la fentre GraphDemo, cliquez sur le bouton Tracer graphique, et vrifier que le graphique apparat comme auparavant. 15. Cliquez nouveau sur Tracer graphique, puis cliquez rapidement sur le bouton Annuler. Si vous tes rapide et cliquez sur le bouton Annuler avant que les donnes du graphique soient gnres, cette action provoque larrt des mthodes excutes par les tches. Les donnes ne sont pas compltes si bien que le graphique apparat avec des trous (voir la figure suivante ; la taille des trous dpend de votre rapidit cliquer sur le bouton Annuler).

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

16. Fermez la fentre GraphDemo et retournez Visual Studio. Vous pouvez dterminer si une tche sest termine ou a t annule en examinant la proprit Status de lobjet Task. La proprit Status contient une valeur extraite de lnumration System.Threading.Tasks.TaskStatus. La liste suivante dcrit certaines de ces valeurs dtat que vous risquez de rencontrer : Created Cest ltat initial dune tche. Elle a t cre, mais na pas encore t planifie pour sexcuter. WaitingToRun sexcuter. Running La tche a t planifie, mais elle na pas encore commenc

La tche est actuellement en cours dexcution dans un thread. La tche sest termine avec succs sans exception non

RanToCompletion gre.

Canceled La tche a t annule avant de dmarrer son excution, ou elle a accus rception dune annulation et sest termine sans lever dexception. Faulted La tche sest termine cause dune exception.

Dans lexercice suivant, vous allez tenter dafficher ltat de chaque tche de telle sorte que vous puissiez voir quand elles se sont termines ou bien quand elles ont t annules.

Annulation dune boucle Parallel For ou ForEach


Les mthodes Parallel.For et Parallel.ForEach ne fournissent pas un accs direct aux objets Task qui ont t crs. En effet, vous ne savez mme pas combien de tches sont excutes (le .NET Framework utilise sa propre heuristique pour dterminer le nombre optimal utiliser en se basant sur les

Partie 6 Cration de solutions professionnelles

ressources disponibles et la charge de travail en cours de lordinateur. Si vous voulez arrter la mthode Parallel.For ou Parallel.ForEach plus tt, vous devez dabord utiliser un objet ParallelLoopState. La mthode que lon spcifie comme corps de la boucle doit inclure un paramtre supplmentaire ParallelLoopState. La TPL cre un objet ParallelLoopState et le passe en paramtre la mthode. La TPL utilise cet objet pour stocker des informations sur chaque invocation de mthode. La mthode peut appeler la mthode Stop de cet objet pour indiquer que la TPL ne doit pas tenter daccomplir des itrations au-del de celles qui ont dj dmarr et qui sont termines. Lexemple suivant montre la mthode Parallel.For qui appelle la mthode doLoopWork chaque itration. La mthode doLoopWork examine la variable ditration ; si elle est suprieure 600, la mthode appelle la mthode Stop du paramtre ParallelLoopState. Cela provoque larrt des itrations ultrieures dans la boucle de la mthode Parallel.For (les itrations en cours dexcution peuvent continuer jusqu leur achvement).

Note Rappelez-vous que les itrations dune boucle Parallel.For ne sexcutent pas dans un ordre spcifique. En consquence, lannulation de la boucle quand la variable ditration a atteint la valeur 600 ne garantit pas que les 599 itrations prcdentes aient dj t excutes. De la mme manire, certaines itrations avec des valeurs suprieures 600 peuvent dj tre termines.
Parallel.For(0, 1000, doLoopWork); ... private void doLoopWork(int i, ParallelLoopState p) { ... if (i > 600) { p.Stop(); } }

Affichage de ltat de chaque tche


1. Dans Visual Studio, dans la fentre Code, trouvez la mthode obtenirDonneesPourGraph. Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches 2. Ajoutez le code suivant illustr en gras cette mthode. Ces instructions gnrent une chane qui contient ltat de chaque tche aprs la fin de leur excution, et elles affichent une bote de message contenant cette chane.
private byte[] obtenirDonneesPourGraph(int tailleDonnees) { ... Task.WaitAll(premiere, deuxieme, troisieme, quatrieme); String message = String.Format("Le Status des tches est {0}, {1}, {2}, {3}", premiere.Status, deuxieme.Status, troisieme.Status, quatrieme.Status); MessageBox.Show(message); } return donnees;

3. Dans le menu Dboguer, cliquez sur Dmarrer sans dbogage. 4. Dans la fentre GraphDemo, cliquez sur le bouton Tracer graphique sans cliquer sur le bouton Annuler. Vrifiez que la bote de message suivante apparat, ce qui indique que ltat des tches est gal RanToCompletion (quatre fois), puis cliquez sur OK. Notez que le graphique apparat seulement aprs que vous avez cliqu sur OK.

5. Dans la fentre GraphDemo, cliquez nouveau sur le bouton Tracer graphique puis cliquez rapidement sur le bouton Annuler. Il est assez tonnant de constater que la bote de message qui apparat affiche toujours ltat de chaque tche RanToCompletion, mme si le graphique apparat avec des trous. Cela est d au fait que malgr la demande dannulation de chaque tche laide du jeton dannulation, les mthodes quelles excutaient se sont simplement arrtes. Le runtime du .NET Framework ne sait pas si les tches ont t annules ou si elles ont t autorises terminer leur travail et ont ignor les demandes dannulation. 6. Fermez la fentre GraphDemo et retournez Visual Studio.

Partie 6 Cration de solutions professionnelles

Dans ces conditions, comment indiquer quune tche a t annule et na pas t autorise terminer son travail ? La rponse se trouve dans lobjet CancellationToken pass comme paramtre la mthode que la tche excute. La classe CancellationToken fournit une mthode appele ThrowIfCancellationRequested. Cette mthode teste la proprit IsCancellationRequested dun jeton dannulation ; si elle est gale true, la mthode lve une exception OperationCanceledException et annule la mthode que la tche excute. Lapplication qui a dmarr le thread doit tre prpare intercepter et grer cette exception, mais cela introduit une autre question. Si une tche se termine en levant une exception, elle retourne en fait ltat Faulted. Cela est vrai, mme si lexception est une exception OperationCanceledException. Une tche entre dans ltat Canceled seulement si elle est annule sans lever une exception. Dans ces conditions, comment une tche peut-elle lever une exception OperationCanceledException sans que cela soit trait comme une exception ? La rponse se trouve dans la tche elle-mme. Pour quune tche reconnaisse quune OperationCanceledException est le rsultat de lannulation dune tche dune manire contrle, et pas simplement une exception provoque par dautres circonstances, elle doit savoir que lopration a bien t annule. Elle ne peut faire cela que si elle peut examiner le jeton dannulation. Vous avez pass ce jeton en paramtre la mthode excute par la tche, mais la tche nexamine en fait aucun de ces paramtres (elle considre que cest la mthode de le faire et ne se sent pas concerne). Au lieu de cela, on spcifie le jeton dannulation quand on cre la tche, soit comme paramtre du constructeur Task, soit comme paramtre de la mthode StartNew de lobjet TaskFactory que lon utilise pour crer et excuter les tches. Le code suivant illustre un exemple bas sur lapplication GraphDemo. Notez la manire dont le paramtre jeton est pass la mthode genererDonneesGraph (comme auparavant), mais aussi en tant que paramtre spar la mthode StartNew :
Task premiere = null; sourceJeton = new CancellationTokenSource(); CancellationToken jeton = sourceJeton.Token; ... premiere = Task.Factory.StartNew(() => genererDonneesGraph(donnees, 0, largeurPixel / 8, jeton), jeton);

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

prsent, quand la mthode excute par la tche lve une exception OperationCanceledException, linfrastructure derrire la tche examine le jeton CancellationToken. Sil indique que la tche a t annule, linfrastructure gre lexception OperationCanceledException, accuse rception de lannulation et dfinit ltat de la tche Canceled. Linfrastructure lve ensuite une exception TaskCanceledException, que votre application doit tre prpare intercepter. Cest ce que vous allez faire dans le prochain exercice, mais avant de raliser cela, vous devez en savoir un peu plus sur la manire dont les tches lvent les exceptions et apprendre les grer.

Gestion des exceptions de tches grce la classe AggregateException


Vous avez vu tout au long de cet ouvrage que la gestion des exceptions est un lment important de toute application commerciale. Les constructions de gestion des exceptions que vous avez rencontres jusqu prsent sont simples utiliser, et si vous les employez avec prcaution, il sagit tout simplement dintercepter une exception et de dterminer quelle partie de code doit tre excute. Cependant, quand on commence diviser le travail en plusieurs tches concurrentes, le suivi et la gestion des exceptions devient un problme plus complexe. La difficult provient que les diffrentes tches peuvent chacune gnrer leurs propres exceptions, et vous avez besoin de trouver un moyen pour intercepter et grer plusieurs exceptions qui peuvent tre leves en mme temps. Cest ce momentl que la classe AggregateException intervient. AggregateException permet dencapsuler une collection dexceptions. Chacune des exceptions de la collection peut tre leve par diffrentes tches. Dans votre application, vous pouvez intercepter lexception AggregateException et parcourir ensuite cette collection pour accomplir tout traitement ncessaire. Pour vous faciliter la tche, la classe AggregateException fournit la mthode Handle. Cette mthode accepte un dlgu Func<Exception, bool> qui rfrence une mthode. La mthode rfrence prend un objet Exception comme paramtre et retourne une valeur Boolean. Quand on appelle Handle, la mthode rfrence sexcute pour chaque exception de la collection de lobjet AggregateException. La mthode rfrence peut examiner lexception et entreprendre laction approprie. Si la mthode rfrence gre lexception, elle doit retourner true. Si ce nest pas le cas, elle doit retourner false. Quand la mthode Handle se termine, toutes les

Partie 6 Cration de solutions professionnelles

exceptions non gres sont regroupes dans une nouvelle AggregateException et cette exception est leve ; un gestionnaire dexception externe ultrieur peut ensuite intercepter cette exception et la traiter. Dans lexercice suivant, vous allez voir comment intercepter une exception AggregateException et lutiliser pour grer lexception TaskCanceledException leve quand une tche est annule.

Accus de rception dannulation et gestion de lexception AggregateException


1. Dans Visual Studio, affichez le fichier GraphWindow.xaml dans la fentre Design. 2. partir de la Bote outils, ajoutez un contrle Label au formulaire, sous le bouton annulerButton. Alignez le bord gauche du contrle Label avec le bord gauche du bouton annulerButton. 3. En utilisant la Fentre de proprits, modifiez la proprit Name du contrle Label en status, et supprimez la valeur de la proprit Content. 4. Retournez la fentre Code qui affiche le fichier GraphWindow.xaml.cs, et ajoutez la mthode suivante sous la mthode obtenirDonneesPourGraph :
private bool gererException(Exception e) { if (e is TaskCanceledException) { tracerButton.Dispatcher.Invoke(new Action(() => { status.Content = "Tches annules"; }), DispatcherPriority.ApplicationIdle); return true; } else { return false; } }

Cette mthode examine lobjet Exception pass comme paramtre ; si cest un objet TaskCanceledException, la mthode affiche le texte Tches annules dans ltiquette status sur le formulaire et retourne true pour indiquer quelle a gr lexception ; dans le cas contraire, elle retourne false. Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches 5. Dans la mthode obtenirDonneesPourGraph, modifiez les instructions qui crent et excutent les tches et spcifiez lobjet CancellationToken comme deuxime paramtre de la mthode StartNew, comme cela est indiqu en gras dans le code suivant :
private byte[] obtenirDonneesPourGraph(int tailleDonnees) { byte[] donnees = new byte[tailleDonnees]; sourceJeton = new CancellationTokenSource(); CancellationToken jeton = sourceJeton.Token; ... premiere = Task.Factory.StartNew(() => genererDonneesGraph(donnees, 0, largeurPixel / 8, jeton), jeton); deuxieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 8, largeurPixel / 4, jeton), jeton); troisieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 4, largeurPixel * 3 / 8, jeton), jeton); quatrieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel * 3 / 8, largeurPixel / 2, jeton), jeton); Task.WaitAll(premiere, deuxieme, troisieme, quatrieme); ... }

6. Ajoutez un bloc try autour des commandes qui crent et excutent les tches, et attendent quelles se terminent. Si lachvement est russi, affichez le texte Tches termines dans ltiquette status sur le formulaire grce la mthode Dispatcher.Invoke. Ajoutez un bloc catch qui gre lexception AggregateException. Dans ce gestionnaire dexception, appelez la mthode Handle de lobjet AggregateException et passez une rfrence la mthode gererException. Le code ci-dessous en gras attire votre attention sur les modifications que vous devez faire :
private byte[] obtenirDonneesPourGraph(int tailleDonnees) { byte[] donnees = new byte[tailleDonnees]; sourceJeton = new CancellationTokenSource(); CancellationToken jeton = sourceJeton.Token; try {

premiere = Task.Factory.StartNew(() => genererDonneesGraph(donnees, 0, largeurPixel / 8, jeton), jeton); deuxieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 8, largeurPixel / 4, jeton), jeton); troisieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel / 4, largeurPixel * 3 / 8, jeton), jeton);

Partie 6 Cration de solutions professionnelles

quatrieme = Task.Factory.StartNew(() => genererDonneesGraph(donnees, largeurPixel * 3 / 8, largeurPixel / 2, jeton), jeton); Task.WaitAll(premiere, deuxieme, troisieme, quatrieme); tracerButton.Dispatcher.Invoke(new Action(() => { status.Content = "Tches termines"; }), DispatcherPriority.ApplicationIdle); } catch (AggregateException ae) { ae.Handle(gererException); } String message = String.Format("Le Status des tches est {0}, {1}, {2}, {3}", premiere.Status, deuxieme.Status, troisieme.Status, quatrieme.Status); MessageBox.Show(message); } return donnees;

7. Dans la mthode generateDataForGraph, remplacez linstruction if qui examine la proprit IsCancellationProperty de lobjet CancellationToken par le code qui appelle la mthode ThrowIfCancellationRequested, comme cela est indiqu ici en gras :
private void generateDataForGraph(byte[] donnees, int partitionDebut, int partitionFin, CancellationToken jeton) { ... for (int x = partitionDebut; x < partitionFin; x++); { ... for (double i = -p; I < p; i += 3) { jeton.ThrowIfCancellationRequested(); ... } } ... }

8. Dans le menu Dboguer, cliquez sur Dmarrer sans dbogage. 9. Dans la fentre Graph Demo, cliquez sur le bouton Tracer graphique et vrifiez que ltat de chaque tche est affich en tant que RanToCompletion, que le graphique est gnr et que ltiquette status affiche le message Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

Tches termines . 10. Cliquez nouveau sur le bouton Tracer graphique, puis cliquez rapidement sur le bouton Annuler. Si vous tes rapide, ltat dune ou plusieurs tches doit tre affich en tant que Canceled, ltiquette status doit afficher le texte Tches annules , et le graphique doit tre affich avec des trous. Si vous ntes pas assez rapide, rptez cette tape pour obtenir ce rsultat. 11. Fermez la fentre Graph Demo et retournez Visual Studio.

Utilisation de continuations avec des tches annules ou en chec


Si vous devez accomplir un travail supplmentaire quand une tche est annule ou lve une exception non gre, rappelez-vous que vous pouvez utiliser la mthode ContinueWith avec la valeur approprie TaskContinuationOptions. Par exemple, le code suivant cre une tche qui excute la mthode faireTravail. Si la tche est annule, la mthode ContinueWith spcifie quune autre tche doit tre cre et excuter la mthode doCancellationWork. Cette mthode peut accomplir une simple routine de connexion ou bien une remise en ordre. Si la tche nest pas annule, la continuation ne sexcute pas.
Task tache = new Task(faireTravail); tache.ContinueWith(doCancellationWork, TaskContinuationOptions.OnlyOnCanceled); tache.Start(); ... private void faireTravail() { // La tche excute ce code quand elle est dmarre ... } ... private void doCancellationWork(Task tache) { // La tche excute ce code quand faireTravail se termine ... }

De la mme manire, vous pouvez indiquer la valeur TaskContinuationOptions.OnlyOnFaulted pour spcifier une continuation qui sexcute si la mthode originale excute par la tche lve une exception non gre.

Partie 6 Cration de solutions professionnelles

Dans ce chapitre, vous avez appris pourquoi il est important dcrire des applications qui peuvent voluer pour des machines dotes de plusieurs processeurs. Vous avez vu comment utiliser la Bibliothque parallle de tches pour excuter des oprations en parallle, et comment synchroniser des oprations concurrentes et attendre quelles se terminent. Vous avez aussi appris utiliser la classe Parallel pour parallliser certaines constructions courantes de programmation, et vous avez vu quand la paralllisation du code nest pas approprie. Vous avez utilis les tches et les threads ensemble dans une interface graphique utilisateur afin damliorer la ractivit et le dbit. Enfin, vous avez vu comment annuler des tches de manire propre et contrle.

Aide-mmoire du chapitre 27
Pour
Crer une tche et lexcuter

Accomplissez
Utilisez la mthode StartNew dun objet TaskFactory pour crer et excuter la tche en une seule tape :
Task tache = taskFactory.StartNew(faireTravail()); ... private void faireTravail() { // La tche excute ce code quand elle est dmarre ... }

Ou crez un nouvel objet Task qui rfrence une mthode excuter et appelez la mthode Start :
Task tache = new Task(faireTravail); tache.Start();

Attendre quune tche se termine

Appelez la mthode Wait de lobjet Task :


Task tache = ...; ... tache.Wait();

Attendre que plusieurs tches se terminent

Appelez la mthode statique WaitAll de la classe Task, et spcifiez les tches attendre :
Task tache1 = ...;

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Chapitre 27 Introduction la Bibliothque parallle de tches

Pour

Accomplissez
Task tache2 = ...; Task tache3 = ...; Task tache4 = ...; ... Task.WaitAll(tache1, tache2, tache3, tache4);

Spcifier une mthode excuter dans une nouvelle tche quand une tche est termine

Appelez la mthode ContinueWith de la tche, et spcifiez la mthode en tant que continuation :


Task tache = new Task(faireTravail); tache.ContinueWith(faireAutreTravail, TaskContinuationOptions.NotOnFaulted);

Retourner une valeur partir dune tche

Utilisez un objet Task<TResult> pour excuter une mthode, o le paramtre type T spcifie le type de la valeur de retour de la mthode. Utilisez la proprit Result de la tche attendre pour que la tche se termine et retournez la valeur :
Task<int> tacheCalculerValeur = new Task<int>(() => calculerValeur(...)); tacheCalculerValeur.Start(); // Invoque la mthode calculerValeur ... int donneesCalculees = tacheCalculerValeur.Result; // Bloque jusqu ce que tacheCalculerValeur se termine

Accomplir des itrations de boucles Utilisez les mthodes Parallel.For et Parallel.ForEach pour accomplir des itrations de boucles grce des tches : et des squences dinstructions grce des tches parallles
Parallel.For(0, 100, performLoopProcessing); ... private void performLoopProcessing(int x) { // Accomplit le traitement de la boucle }

Utilisez la mthode Parallel.Invoke pour accomplir les appels de mthodes concurrentes grce des tches spares :
Parallel.Invoke( faireTravail, faireAutreTravail, doYetMoreWork );

Grer des exceptions leves par

Interceptez lexception AggregateException. Utilisez la mthode Handle

Partie 6 Cration de solutions professionnelles

Pour
une ou plusieurs tches

Accomplissez
pour spcifier une mthode qui peut grer chaque exception de lobjet AggregateException. Si la mthode qui gre lexception peut la prendre en charge, retournez true ; dans le cas contraire, retournez false :
try {

} catch (AggregateException ae) { ae.Handle(new Func<Exception, bool> (gererException)); } ... private bool gererException(Exception e) { if (e is TaskCanceledException) { ... return true; } else { return false; } }

Task tache = Task.Factory.StartNew(...); ...

Prendre en charge lannulation dune tche

Implmentez lannulation cooprative en crant un objet CancellationTokenSource et en utilisant le paramtre CancellationToken dans la mthode excute par la tche. Dans la mthode de la tche, appelez la mthode ThrowIfCancellationRequested du paramtre CancellationToken pour lever une exception OperationCanceledException et terminez la tche :
private void genererDonneesGraph(..., CancellationToken jeton) { ... jeton.ThrowIfCancellationRequested(); ... }

Dunod 2010 Visual C# 2010 tape par tape John Sharp

Vous aimerez peut-être aussi