Vous êtes sur la page 1sur 40

CS_doNet/05 Page 117 Mercredi, 14.

mai 2003 10:32 10

5
Processus, threads et gestion de la synchronisation
Nous exposons ici les notions fondamentales que sont les processus et les threads, dans larchitecture des systmes dexploitation de type Windows NT/2000/XP Il faut avoir lesprit que le CLR . (ou moteur dexcution) dcrit dans le chapitre prcdent est une couche logicielle charge par lhte du moteur dexcution lorsquun assemblage .NET est lanc.

Introduction
Un processus (process en anglais) est concrtement une zone mmoire contenant des ressources. Les processus permettent au systme d'exploitation de rpartir son travail en plusieurs units fonctionnelles. Un processus possde une ou plusieurs units dexcution appele(s) threads. Un processus possde aussi un espace dadressage virtuel priv accessible en lecture et criture, seulement par ses propres threads. Dans le cas des programmes .NET, un processus contient aussi dans son espace mmoire la couche logicielle appele CLR ou moteur dexcution. La description du CLR fait lobjet du chapitre prcdent. Cette couche logicielle est charge ds la cration du processus par lhte du moteur dexcution (ceci est dcrit page 79). Un thread ne peut appartenir qu un processus et ne peut utiliser que les ressources de ce processus. Quand un processus commence, le systme dexploitation lui associe automatiquement un thread appel thread principal (main thread ou primary thread en anglais). Cest ce thread qui excute lhte du moteur dexcution, le chargeur du CLR.

CS_doNet/05 Page 118 Mercredi, 14. mai 2003 10:32 10

118

Chapitre 5 - Processus, threads et gestion de la synchronisation

Une application est constitue dun ou plusieurs processus cooprants. Par exemple lenvironnement de dveloppement Visual Studio .NET est une application, qui peut utiliser un processus pour diter les chiers sources et un processus pour la compilation. Sous les systmes dexploitation Windows NT/2000/XP on peut visualiser un instant , donn toutes les applications et tous les processus en lanant le gestionnaire des tches (task manager en anglais). Il est courant davoir une trentaine de processus en mme temps, mme si vous avez ouvert un petit nombre dapplications. En fait le systme excute un grand nombre de processus, un pour la gestion de la session courante, un pour la barre des tches, et bien dautres encore. Figure 5-1. Vue des processus avec le gestionnaire des tches

Les processus
Introduction
Dans un systme dexploitation Windows 32 bits, tournant sur un processeur 32 bits, un processus peut tre vu comme un espace linaire mmoire de 4Go (2^32 octets), de ladresse 0x00000000 0xFFFFFFFF. Cet espace de mmoire est dit priv, car inaccessible par les autres processus. Cet espace se partage en 2Go pour le systme et 2Go pour lutilisateur. Windows et certains processeurs soccupent de faire lopration de translation entre cet espace dadressage virtuel et lespace dadressage rel. Si N processus tournent sur une machine il nest (heureusement) pas ncessaire davoir Nx4Go de RAM.

CS_doNet/05 Page 119 Mercredi, 14. mai 2003 10:32 10

Les processus

119

Windows alloue seulement la mmoire ncessaire au processus, 4Go tant la limite suprieure dans un environnement 32 bits. Un mcanisme de mmoire virtuelle du systme sauve sur le disque dur et charge en RAM des morceaux de processus appels pages mmoire (chaque page a une taille de 4Ko). L encore tout ceci est transparent pour le dveloppeur et lutilisateur.

La classe System.Diagnostics.Process
Une instance de la classe System.Diagnostics.Process rfrence un processus. Les processus qui peuvent tre rfrencs sont : Le processus courant dans lequel linstance est utilise. Un processus sur la mme machine autre que le processus courant. Un processus sur une machine distante.

Les mthodes et champs de cette classe permettent de crer, dtruire, manipuler ou obtenir des informations sur ces processus. Nous exposons ici quelques techniques courantes dutilisation de cette classe.

Crer et dtruire un processus ls


Le petit programme suivant cre un nouveau processus, appel processus fils. Dans ce cas le processus initial est appel processus parent. Ce processus ls excute le bloc note. Le thread du processus parent attend une seconde avant de tuer le processus ls. Le programme a donc pour effet douvrir et de fermer le bloc note : Exemple 5-1.
using System.Diagnostics; using System.Threading; namespace ProcessTest1 { class Prog { static void Main(string[] args) { // cre un processus fils qui lance notepad.exe // avec le fichier texte hello.txt Process p = Process.Start("notepad.exe","hello.txt"); // endors le thread 1 seconde Thread.Sleep(1000); // tue le processus fils p.Kill(); } } }

Notez que la mthode statique Start() peut utiliser les associations qui existe sur un systme dexploitation, entre un programme et une extension de chier. Concrtement ce programme a le mme comportement, si lon crit :
Process p = Process.Start("hello.txt");

CS_doNet/05 Page 120 Mercredi, 14. mai 2003 10:32 10

120

Chapitre 5 - Processus, threads et gestion de la synchronisation

Empcher de lancer deux fois le mme programme sur la mme machine


Cette fonctionnalit est requise par de nombreuses applications. En effet, il est courant quil ny a pas de sens lancer simultanment plusieurs fois une mme application sur la mme machine. Par exemple il ny a pas de sens lancer plusieurs fois WindowMessenger sur la mme machine. Jusquici, pour satisfaire cette contrainte sous Windows, les dveloppeurs utilisaient le plus souvent la technique dite du mutex nomm dcrite page 138. Lutilisation de cette technique pour satisfaire cette contrainte souffre des dfauts suivants : Il y a le risque faible, mais potentiel, que le nom du mutex soit utilis par une autre application, auquel cas cette technique ne marche absolument plus et peut provoquer des bugs difciles dtecter. Cette technique ne peut rsoudre le cas gnral o lon nautorise que N instances de lapplication.

Grce aux mthodes statiques GetCurrentProcess() (qui retourne le processus courant) et GetProcesses() (qui retourne tous les processus lancs sur la machine) de la classe System.Diagnostics.Process, ce problme trouve une solution lgante et trs facile implmenter comme le prouve le programme suivant : Exemple 5-2.
using System; using System.Diagnostics; namespace ProcessTest2 { class Prog { static void Main(string[] args) { if( TestSiDejaLanc() ) { Console.WriteLine("Ce programme est dj lanc."); } else { // ici le code de l'application } } static bool TestSiDejaLanc() { Process pcur = Process.GetCurrentProcess(); Process[] ps = Process.GetProcesses(); foreach( Process p in ps ) if( pcur.Id != p.Id ) if(pcur.ProcessName == p.ProcessName ) return true;

CS_doNet/05 Page 121 Mercredi, 14. mai 2003 10:32 10

Les threads

121

return false; } } }

Notez que la mthode GetProcesses() peut aussi retourner tous les processus sur une machine distante en communiquant comme argument le nom de la machine distante.

Les threads
Introduction
Un thread comprend : Un compteur d'instructions, qui pointe vers linstruction en cours dexcution ; Une pile (dcrite page 100) ; Un ensemble de valeurs pour les registres, dnissant une partie de ltat du processeur excutant le thread ; Une zone prive de donnes.

Tous ces lments sont rassembls sous le nom de contexte dexcution du thread. L'espace d'adressage et, par consquent, toutes les ressources qui y sont stockes, sont communs tous les threads dun mme processus. Nous ne parlerons pas de lexcution des threads en mode noyau et en mode utilisateur. Ces deux modes, utiliss par Windows depuis bien avant .NET, existent toujours puisquils se situent dans une couche en dessous du CLR. Nanmoins ces modes ne sont absolument pas visibles du Framework .NET. Lutilisation en parallle de plusieurs threads constitue souvent une rponse naturelle limplmentation des algorithmes. En effet, les algorithmes utiliss dans les logiciels sont souvent constitus de tches dont les excutions peuvent se faire en parallle. Attention, utiliser une grande quantit de threads gnre beaucoup de context switching, et nalement nuit aux performances.

Notion de thread gr
Il faut bien comprendre que les threads qui excutent les applications .NET sont bien ceux de Windows. Cependant on dit quun thread est gr quand le CLR connat ce dernier. Concrtement, un thread est gr sil est cr par du code gr. Si le thread est cr par du code non gr, alors il nest pas gr. Cependant ce thread devient gr ds quil excute du code gr (voir page 247 pour un exemple de code non gr qui appelle du code gr). Un thread gr se distingue dun thread non gr par le fait que le CLR cre une instance de la classe System.Threading.Thread pour le reprsenter et le manipuler. En interne, le CLR garde une liste des threads grs, appele ThreadStore.

CS_doNet/05 Page 122 Mercredi, 14. mai 2003 10:32 10

122

Chapitre 5 - Processus, threads et gestion de la synchronisation

Le CLR fait en sorte que chaque thread gr cr soit excut au sein dun domaine dapplication, un instant donn. Cependant un thread nest absolument pas cantonn un domaine dapplication, et il peut en changer au cours du temps. La notion de domaine dapplication est prsente page 15. Notez quen appliquant lattribut ThreadStaticAttribute un champ statique dune classe, vous spciez au CLR que ce champ nest pas partag entre plusieurs threads. Concrtement, chaque thread aura sa version de ces champs. La notion dattribut est prsente page 187. Dans le domaine de la scurit, lutilisateur principal dun thread gr est indpendant de lutilisateur principal du thread non gr sous-jacent. Tout ceci est dtaill page 180.

Le multitche premptif
On peut se poser la question suivante : mon ordinateur a un processeur (voir deux) et pourtant le gestionnaire des tches indique quune centaine de threads tournent simultanment sur ma machine ! Comment cela est-il possible ? Cela est possible grce au multitche premptif qui gre lordonnancement des threads. Une partie du noyau de Windows, appele rpartiteur (scheduler en anglais), segmente le temps en portions appeles quantum (appeles aussi time slices). Ces intervalles de temps sont de lordre de quelques millisecondes et ne sont pas de dure constante. Pour chaque processeur, chaque quantum est allou un seul thread. La succession trs rapide des threads donne l'illusion l'utilisateur que les threads sexcutent simultanment. On appelle contexte switching lintervalle entre deux quantums conscutifs. Un avantage de cette mthode est que les threads en attente dune ressource nont pas dintervalle de temps allou jusqu la disponibilit de la ressource. Ladjectif premptif utilis pour une telle gestion du multitche vient du fait que les threads sont interrompus dune manire autoritaire par le systme. Pour les curieux sachez que durant le context switching, le systme dexploitation place une instruction de saut vers le prochain context switching dans le code qui va tre excut par le prochain thread. Cette instruction est de type interruption software. Si le thread doit sarrter avant de rencontrer cette instruction (par exemple parce quil est en attente dune ressource) cette instruction est automatiquement enleve et le context switching a lieu prmaturment. Linconvnient majeur du multitche premptif est la ncessit de protger les ressources dun accs anarchique avec des mcanismes de synchronisation (dcrit page 128). Il existe thoriquement un autre modle de la gestion du multitche, o la responsabilit de dcider quand donner la main incombe au threads eux-mmes, mais ce modle est dangereux car les risques de ne jamais rendre la main sont trop grands. Les systmes dexploitation Windows nimplmentent plus que le multitche premptif.

Les niveaux de priorit dexcution


Certaines tches sont plus prioritaires que dautres. Concrtement elles mritent que le systme dexploitation leur alloue plus de temps processeur. Par exemple, certains pilotes de priphrique, pris en charge par le processeur principal, ne doivent pas tre inter-

CS_doNet/05 Page 123 Mercredi, 14. mai 2003 10:32 10

Les threads

123

rompus. Une autre catgorie de tches prioritaires sont les interfaces graphiques utilisateurs. En effet, les utilisateurs naiment pas attendre que linterface se rafrachisse. Ceux qui viennent du monde win32, savent bien que le systme dexploitation Windows, sous-jacent au CLR, assigne un numro de priorit chaque thread entre 0 et 31. Cependant, il nest pas dans la philosophie de la gestion des threads sous .NET de travailler directement avec cette valeur, parce que : Elle est peu explicite. Cette valeur est susceptible de changer au cours du temps.

Niveau de priorit dun processus


Vous pouvez assigner une priorit vos processus avec la proprit PriorityClass de la classe System.Diagnostics.Process. Cette proprit est de type lnumration System.Diagnostics.ProcessPriorityClass qui a les valeurs suivantes : Valeur de ProcessPriorityClass Low BelowNormal Normal AboveNormal High RealTime Niveau de priorit correspondant 4 6 8 10 13 24

Notez que le processus propritaire de la fentre en premier plan voit sa priorit incrmente dune unit si la proprit PriorityBoostEnabled de la classe System.Diagnostics.Process est positionne true. Cette proprit est par dfaut positionne true. Cette proprit nest accessible sur une instance de la classe Process, que si celle-ci rfrence un processus sur la mme machine. Vous avez la possibilit de changer la priorit dun processus en utilisant le gestionnaire des tches comme ceci :

CS_doNet/05 Page 124 Mercredi, 14. mai 2003 10:32 10

124

Chapitre 5 - Processus, threads et gestion de la synchronisation


Figure 5-2. Changement de la priorit d'un processus

Les systmes dexploitation Windows ont un processus dinactivit (idle en anglais) qui a la priorit 0. Cette priorit nest accessible aucun autre processus. Par dnition lactivit des processeurs, note en pourcentage, est : 100% moins le pourcentage de temps pass dans le thread du processus dinactivit.

Niveau de priorit dun thread


Chaque thread peut dnir sa propre priorit par rapport celle de son processus, avec la proprit Priority de la classe System.Threading.Thread. Cette proprit est de type lnumration System.Threading.ThreadPriority qui prsente les valeurs suivantes : Valeur de ThreadPriority Lowest BelowNormal Normal AboveNormal Highest Effet sur la priorit du thread -2 units par rapport la priorit du processus 1 unit par rapport la priorit du processus mme priorit que la priorit du processus +1 unit par rapport la priorit du processus +2 units par rapport la priorit du processus

CS_doNet/05 Page 125 Mercredi, 14. mai 2003 10:32 10

Les threads

125

Dans la plupart de vos applications, vous naurez pas modier la priorit de vos processus et threads, qui par dfaut, est assigne Normal.

La classe System.Threading.Thread
Chaque thread gr a un objet de la classe System.Threading.Thread qui lui est associ automatiquement par le CLR. Vous pouvez utiliser cet objet pour manipuler le thread partir dun autre thread ou partir du thread lui-mme. Vous pouvez obtenir cet objet associ au thread courant avec la proprit statique CurrentThread de la classe System.Threading.Thread :
using System.Threading; ... Thread tcur = Thread.CurrentThread;

Une fonctionnalit de la classe Thread, bien pratique pour dboguer une application multithread (multitches), est la possibilit de pouvoir nommer ses threads avec une chane de caractres :
tcur.Name = "thread Foo";

Crer et joindre un thread


Pour crer un nouveau thread dans le processus courant, il suft de crer un nouvel objet de la classe Thread. Le seul constructeur de cette classe prend en argument un objet de type ThreadStart qui rfrence la mthode qui va tre excute par le thread cr. Une telle classe, qui sert rfrencer des mthodes est appele dlgation. Les dlgations sont dcrites page 401. Aprs sa cration le thread ne commence vraiment sexcuter qu lappel de la mthode Start(). Exemple 5-3.
using System; using System.Threading; namespace ThreadTest { class Prog { static void f1(){Console.WriteLine("hello1");} void f2(){Console.WriteLine("hello2");} static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart( f1 )); Prog P = new Prog(); Thread t2 = new Thread(new ThreadStart( P.f2 )); t1.Start(); t2.Start(); t1.Join(); t2.Join(); } } }

CS_doNet/05 Page 126 Mercredi, 14. mai 2003 10:32 10

126

Chapitre 5 - Processus, threads et gestion de la synchronisation

Comme lillustre cet exemple, la mthode rfrence par un dlgu de type ThreadStart peut tre statique ou non. Dans cet exemple, nous utilisons la mthode Join(), qui suspend lexcution du thread courant jusqu ce que le thread sur lequel sapplique cette mthode ait termin. Cette mthode existe aussi en une version surcharge qui prend en paramtre un entier qui dnie le nombre maximal de millisecondes attendre la n du thread (i.e un TimeOut). Cette version de Join() retourne un boolen positionn true si le thread sest effectivement termin.

Suspendre lactivit dun thread


Vous avez la possibilit de suspendre lactivit dun thread pour une dure dtermine en utilisant la mthode Sleep() de la classe Thread. Vous pouvez spcier la dure au moyen dun entier qui dsigne un nombre de millisecondes ou avec une instance de la structure System.TimeSpan. Bien quune telle instance puisse spcier une dure avec la prcision du dixime de milliseconde (100 nano-secondes) la granularit temporelle de la mthode Sleep() nest qu la milliseconde.
// le thread courant est suspendu pour une seconde Thread.Sleep(1000) ;

On peut aussi suspendre lactivit dun thread en appelant la mthode Suspend() de la classe Thread, partir dun autre thread ou partir du thread suspendre. Dans les deux cas le thread se bloque jusqu ce quun autre thread appelle la mthode Resume() de la classe Thread. Contrairement la mthode Sleep(), un appel Suspend() ne suspend pas immdiatement le thread, mais le CLR suspendra ce thread au prochain point protg rencontr. La notion de point protg est prsente page 107.

Terminer un thread
Un thread peut se terminer selon trois scnarios : Il sort de la mthode sur laquelle il avait commenc (la mthode Main() pour le thread principal, la mthode rfrence par le dlgu ThreadStart pour les autres threads). Il sauto-interrompt (il se suicide). Il est interrompu par un autre thread.

Le premier cas tant trivial, nous ne nous intressons quaux deux autres cas. Dans ces deux cas, la mthode Abort() peut tre utilise (par le thread courant ou par un thread extrieur). Elle provoque lenvoi dune exception de type ThreadAbortException. Cette exception a la particularit dtre relance automatiquement lorsquelle est rattrape par un gestionnaire dexception car le thread est dans un tat spcial nomm AbortRequested. Seul lappel la mthode statique ResetAbort() (si on dispose de la permission ncessaire) dans le gestionnaire dexception empche cette propagation.

CS_doNet/05 Page 127 Mercredi, 14. mai 2003 10:32 10

Les threads
Exemple 5-4. Suicide du thread principal
using System; using System.Threading; namespace ThreadTest { class Prog { static void Main(string[] args) { Thread t = Thread.CurrentThread; try { t.Abort(); } catch(ThreadAbortException e) { Thread.ResetAbort(); } } } }

127

Lorsquun thread A appelle la mthode Abort() sur un autre thread B, il est conseill que A attende que B soit effectivement termin en appelant la mthode Join() sur B. Il existe aussi la mthode Interrupt() qui permet de terminer un thread lorsquil est dans un tat dattente (i.e bloqu sur une des mthodes Wait(), Sleep() ou Join()). Cette mthode a un comportement diffrent selon que le thread terminer est dans un tat dattente ou non. Si le thread terminer est dans un tat dattente lorsque Interrupt() est appele par un autre thread, lexception ThreadInterruptedException est lance. Si le thread terminer nest pas dans un tat dattente lorsque Interrupt() est appele, la mme exception sera lance ds que ce thread rentrera dans un tat dattente. Le comportement est le mme si le thread terminer appelle Interrupt() sur luimme.

Notion de threads foreground et background


La classe Thread prsente la proprit boolenne IsBackground. Un thread foreground est un thread qui empche la terminaison du processus tant quil nest pas termin. A loppos un thread background est un thread qui est termin automatiquement par le CLR (par lappel la mthode Abort()) lorsquil ny a plus de thread foreground dans le processus concern. IsBackground est positionne false par dfaut, ce qui fait que les threads sont foreground par dfaut. On pourrait traduire thread foreground par thread de premier plan, et thread background par thread de fond.

CS_doNet/05 Page 128 Mercredi, 14. mai 2003 10:32 10

128

Chapitre 5 - Processus, threads et gestion de la synchronisation

Diagramme dtats dun thread


La classe Thread a le champ ThreadState de type lnumration System.Threading.ThreadState. Les valeurs de cette numration sont :
Aborted Running Suspended WaitSleepJoin AbortRequested Stopped SuspendRequested BackgroundR StopRequested Unstarted

La description de chacun de ces tats se trouve dans larticle ThreadState Enumeration des MSDN. Cette numration est un indicateur binaire, cest--dire que ses instances peuvent prendre plusieurs valeurs la fois. Par exemple un thread peut tre la fois dans ltat Running AbortRequested et Background. La notion dindicateur binaire est prsente page 301. Daprs ce quon a vu dans la section prcdente, on peut dnir le diagramme dtat simpli suivant : Figure 5-3. Diagramme dtat dun thread, simplifi Unstarted Start() ISuspend() Running Resume() Abort() Stopped Suspended Wait() Sleep() Join() WaitSleepJoin Interrupt()

Les objets de synchronisation


Introduction
En informatique, le mot synchronisation ne peut tre utilis que dans le cas des applications multithread (mono ou multi-processus). En effet, la particularit de ces applications est davoir plusieurs units dexcution, do possibilit de conits daccs aux ressources. Les objets de synchronisation sont des objets partageables entre threads excuts sur la mme machine. Le propre dun objet de synchronisation est de pouvoir bloquer un des threads utilisateur jusqu la ralisation dune condition par un autre thread.

CS_doNet/05 Page 129 Mercredi, 14. mai 2003 10:32 10

Les objets de synchronisation

129

Comme nous allons le voir, il existe de nombreuses classes et mcanismes de synchronisation. Chacun rpond un ou plusieurs besoins spciques et il est ncessaire davoir assimil tout ce chapitre avant de concevoir une application professionnelle multithreads utilisant la synchronisation. Nous nous sommes efforc de souligner les diffrences, surtout les plus subtiles, qui existent entre les diffrents mcanismes. Quand vous aurez compris les diffrences, vous serez capable dutiliser ces mcanismes. Synchroniser correctement un programme est une des tches du dveloppement logiciel les plus subtiles. Le sujet remplit de nombreux ouvrages. Avant de vous plonger dans des spcications compliques, soyez certains que lutilisation de la synchronisation est incontournable. Souvent lutilisation de quelques rgles simples suft viter davoir grer la synchronisation. Parmi ces rgles, citons la rgle dafnit entre threads et ressources. Cette rgle dit que pour viter davoir grer la synchronisation des accs une ressource, il suft de faire en sorte quelle soit toujours accde par le mme thread.

Notion de deadlocks et de race conditions


Avant daborder les mcanismes de synchronisation, il est ncessaire davoir une ide prcise des notions de race conditions (situations de comptition en franais) et de deadlocks (interblocages en franais).

Race conditions
Il sagit dune situation o des actions effectues par des units dexcution diffrentes senchanent dans un ordre illogique, entranant des tats non prvus. Par exemple un thread T modie une ressource R, rend les droits daccs dcriture R, reprend les droits daccs en lecture sur R et utilise R comme si son tat tait celui dans lequel il lavait laiss. Pendant lintervalle de temps entre la libration des droits daccs en criture et lacquisition des droits daccs en lecture, il se peut quun autre thread ait modi ltat de R. Un autre exemple classique de situation de comptition est le modle producteur consommateur. Le producteur utilise souvent le mme espace physique pour stocker les informations produites. En gnral on noublie pas de protger cet espace physique des accs concurrents entre producteurs et consommateurs. On oublie plus souvent que le producteur doit sassurer quun consommateur a effectivement lu une ancienne information avant de produire une nouvelle information. Si lon ne prend pas cette prcaution, on sexpose au risque de produire des informations qui ne seront jamais consommes. Les consquences de situations de comptition mal gres peuvent tre des failles dans un systme de scurit. Une autre application peut forcer un enchanement dactions non prvues par les dveloppeurs. Typiquement il faut absolument protger laccs en criture un boolen qui conrme ou inrme une authentication. Sinon il se peut que son tat soit modi entre linstant ou ce boolen est positionn par le mcanisme dauthentication et linstant ou ce boolen est lu pour protger des accs des ressources. De clbres cas de failles de scurit dues une mauvaise gestion des situations de comptition ont exist. Une de celles-ci concernait notamment le noyau Unix.

CS_doNet/05 Page 130 Mercredi, 14. mai 2003 10:32 10

130

Chapitre 5 - Processus, threads et gestion de la synchronisation

Deadlocks
Il sagit dune situation de blocage cause de deux ou plusieurs units dexcution qui s'attendent mutuellement. Par exemple : Un thread T1 acquiert les droits daccs sur la ressource R1. Un thread T2 acquiert les droits daccs sur la ressource R2. T1 demande les droits daccs sur R2 et attend, car cest T2 qui les possde. T2 demande les droits daccs sur R1 et attend, car cest T1 qui les possde. T1 et T2 attendront donc indniment, la situation est bloque ! Il existe trois techniques diffrentes pour viter ce problme qui est plus subtil que la plupart des bugs que lon rencontre. Nautoriser aucun thread avoir des droits daccs sur plusieurs ressources simultanment. Dnir une relation dordre dans lacquisition des droits daccs aux ressources. Cest--dire quun thread ne peut acqurir les droits daccs sur R2 sil na pas dj acquis les droits daccs sur R1. Naturellement la libration des droits daccs se fait dans lordre inverse de lacquisition. Systmatiquement dnir un temps maximum dattente (timeout) pour toutes les demandes daccs aux ressources et traiter les cas dchec. Pratiquement tous les mcanismes de synchronisation .NET offrent cette possibilit.

Les deux premires techniques sont plus efcaces mais aussi plus difcile implmenter. En effet, elles ncessitent chacune une contrainte trs forte et difcile maintenir durant lvolution de lapplication. En revanche les situations dchecs sont inexistantes. Les gros projets utilisent systmatiquement la troisime technique. En effet, si le projet est gros, le nombre de ressources est en gnral trs grand. Dans ces projets, les conits daccs simultans une ressource sont donc des situations marginales. La consquence est que les situations dchec sont, elles-aussi, marginales.

Les champs volatiles


Un champ dun type peut tre accd par plusieurs threads. Supposons que ces accs, en lecture ou en criture, ne soient pas synchroniss. Dans ce cas, les nombreux mcanismes internes de la gestion du code lexcution, font quil ny a pas de garantie que chaque accs en lecture au champ charge la valeur la plus rcente. Un champ dclar volatile vous donne cette garantie. En langage C#, un champ est dclar volatil si le mot-cl volatile est crit devant sa dclaration. Tous les champs ne peuvent pas tre volatiles. Il y a une restriction sur le type du champ. Pour quun champ puisse tre volatile, il faut que son type soit dans cette liste : Un type rfrence. Un pointeur (dans une zone de code non protge). sbyte, byte, short, ushort, int, uint, char, float, bool (double, long et ulong, la condition de travailler avec une machine 64 bits).

CS_doNet/05 Page 131 Mercredi, 14. mai 2003 10:32 10

Les objets de synchronisation

131

Une numration dont le type sous-jacent est parmi : byte, sbyte, short, ushort, int, uint (double, long et ulong, la condition de travailler avec une machine 64 bits).

Comme vous laurez remarquez, seuls les types dont la valeur ou la rfrence fait au plus le nombre doctets dun entier natif (quatre ou huit), peuvent tre volatiles. Cela implique que les oprations concurrentes sur une valeur de plus de ce nombre doctets (une grosse structure par exemple) doit utiliser les mcanismes de synchronisation prsents ci-aprs.

La classe System.Threading.Interlocked
Lexprience a montr que les ressources protger dans un contexte multi threads sont trs souvent des variables entires. Les oprations les plus courantes ralises par les threads sur ces variables entires partages, sont lincrmentation et la dcrmentation dune unit. Le Framework .NET prvoit donc un mcanisme spcial avec la classe System.Threading.Interlocked pour ces oprations trs spciques, mais aussi trs courantes. Cette classe deux mthodes statiques Increment() et Decrement(), qui incrmente ou dcrmente une variable entire de type int ou long passe par rfrence. On dit que lutilisation de la classe Interlocked rend ces oprations atomiques (cest--dire indivisibles, comme ce que lon pensait il y a quelques dcennies pour les atomes). Le programme suivant prsente laccs concurrent de deux threads la variable entire Compteur. Un thread lincrmente cinq fois tandis que lautre la dcrmente cinq fois. Exemple 5-5.
using System; using System.Threading; namespace InterlockedTest { class Prog { static long Compteur =1; static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart( f1 )); Thread t2 = new Thread(new ThreadStart( f2 )); t1.Start();t2.Start(); t1.Join(); t2.Join(); } static void f1() { for(int i =0;i<5;i++) { Interlocked.Increment( ref Compteur ); Console.WriteLine("Compteur++ {0}",Compteur); Thread.Sleep(10); } }

CS_doNet/05 Page 132 Mercredi, 14. mai 2003 10:32 10

132

Chapitre 5 - Processus, threads et gestion de la synchronisation

static void f2() { for(int i =0;i<5;i++) { Interlocked.Decrement( ref Compteur ); Console.WriteLine("Compteur-- {0}",Compteur); Thread.Sleep(10); } } } }

Ce programme afche ceci (dune manire non dterministe, cest--dire que lafchage pourrait varier dune excution une autre) :
Compteur++ Compteur-Compteur++ Compteur-Compteur++ Compteur-Compteur++ Compteur-Compteur-Compteur++ 2 1 2 1 2 1 2 1 0 1

Si on nendormait pas les threads 10 millimes de seconde chaque modication, les threads auraient le temps de raliser leurs tches en un quantum et il ny aurait pas lentrelacement des excutions, donc pas daccs concurrent.

Autre possibilit dutilisation de la classe Interlocked


La classe Interlocked permet de rendre atomique une autre opration usuelle qui est la recopie de ltat dun objet source vers un objet destination au moyen de la mthode statique surcharge Exchange(). Elle permet aussi de rendre atomique lopration de comparaison des tats de deux objets, et dans le cas dgalit, la recopie de cet tat vers un troisime objet au moyen de la mthode statique surcharge CompareExchange(). (Voir les MSDN pour plus de dtails.)

La classe System.Threading.Monitor et le mot-cl lock


Le fait de rendre des oprations simples atomiques (des oprations comme lincrmentation, la dcrmentation ou la recopie dun tat), est indniablement important mais est loin de couvrir tous les cas o la synchronisation est ncessaire. La classe System.Threading.Monitor permet de rendre nimporte quelle portion de code excutable par un seul thread la fois. On appelle une telle portion de code une section critique.

Les mthodes Enter() et Exit()


La classe Monitor prsente les mthodes statiques Enter(object) et Exit(object). Ces mthodes prennent un objet en paramtre. Cet objet constitue un moyen simple

CS_doNet/05 Page 133 Mercredi, 14. mai 2003 10:32 10

Les objets de synchronisation

133

didentier de manire unique la ressource protger dun accs concurrent. Lorsquun thread appelle la mthode Enter(), il attend davoir le droit exclusif de possder lobjet rfrenc (il nattend que si un thread a dj ce droit). Une fois ce droit acquis et consomm, le thread libre ce droit en appelant Exit() sur ce mme objet. En gnral on veut protger soit un objet particulier soit une classe particulire, des accs concurrents. Le meilleur moyen est : dutiliser les mthodes Enter() et Exit() avec la rfrence this dans les mthodes partages de lobjet protger, dutiliser les mthodes Enter() et Exit() avec la rfrence typeof (le type de la classe courante) dans les mthodes statiques de la classe protger. Un thread peut appeler Enter() plusieurs fois sur le mme objet la condition quil appelle Exit() autant de fois sur le mme objet pour se librer des droits exclusifs. Un thread peut possder des droits exclusifs sur plusieurs objets la fois, mais cela peut mener au problme connu sous le nom de dead lock, prsent un peu plus haut. Il ne faut jamais appeler les mthodes Enter() et Exit() sur un objet de type valeur, comme un entier ! Il faut toujours appeler Exit() dans un bloc finally an dtre certain de librer les droits daccs exclusifs quoi quil arrive. Si dans lexemple de la section page 130, un thread doit lever la variable Compteur au carr tandis que lautre thread doit la multiplier par deux, il faudrait remplacer lutilisation de la classe Interlocked par lutilisation de la classe Monitor. Le code de f1() et f2() serait alors : Exemple 5-6.
... static void f1() { for(int i =0;i<5;i++) { try { Monitor.Enter( typeof(Prog) ); Compteur*=Compteur; } finally { Monitor.Exit( typeof(Prog) ); } Console.WriteLine("Compteur^2 {0}",Compteur); Thread.Sleep(10); }

CS_doNet/05 Page 134 Mercredi, 14. mai 2003 10:32 10

134

Chapitre 5 - Processus, threads et gestion de la synchronisation

} static void f2() { for(int i =0;i<5;i++) { try { Monitor.Enter( typeof(Prog) ); Compteur*=2; } finally { Monitor.Exit( typeof(Prog) ); } Console.WriteLine("Compteur*2 {0}",Compteur); Thread.Sleep(10); } } ...

Il est tentant dcrire Compteur la place de typeof(Prog) mais Compteur est un membre statique de type valeur. De plus les oprations lvation au carr et multiplication par deux ntant pas commutatives, la valeur nale de Compteur est ici non dtermine.

Le mot cl lock de C#
Le langage C# prsente le mot-cl lock qui remplace lgamment lutilisation des mthode Enter() et Exit(). La mthode f1() pourrait donc scrire : Exemple 5-7.
... static void f1() { for(int i =0;i<5;i++) { lock( typeof(Prog) ) { Compteur*=Compteur; } Console.WriteLine("Compteur^2 {0}",Compteur); Thread.Sleep(10); } } ...

CS_doNet/05 Page 135 Mercredi, 14. mai 2003 10:32 10

Les objets de synchronisation

135

A linstar des blocs for et if, les blocs dnis par le mot-cl lock ne sont pas tenus davoir des accolades sils ne contiennent quune instruction. On aurait donc pu crire :
... lock( typeof(Prog) ) Compteur*=Compteur; ...

Autres mthodes de la classe Monitor


La classe Monitor prsente dautres mthodes statiques qui lui confrent des fonctionnalits inaccessibles par lutilisation des seules mthodes Enter() et Exit().
bool TryEnter(object [,int] )

Cette mthode est similaire Enter() mais elle nest pas bloquante. Si les droit daccs exclusifs sont dj possds par un autre thread, cette mthode retourne immdiatement et sa valeur de retour est false. On peut aussi rendre un appel TryEnter() bloquant pour une dure limite spcie en millisecondes. Puisque lissue de cette mthode est incertaine, et que dans le cas o lon acquerrait les droits daccs exclusifs il faudrait les librer dans un bloc finally, il est conseill de sortir immdiatement de la mthode courante dans le cas o lappel TryEnter() chouerait :
... try { if( ! Monitor.TryEnter(x) ) return; // ... } finaly { Monitor.Exit(x); } ... Wait(object [,int]) ; Pulse(object); PulseAll(object)

Les trois mthodes Wait() Pulse() et PulseAll() doivent tre utilises ensembles et ne peuvent tre correctement comprises sans un petit scnario. Lide est quun thread ayant les droits daccs exclusifs un objet dcide dattendre (en appelant Wait()) que ltat de lobjet change. Pour cela, ce thread doit accepter de perdre momentanment les droit daccs exclusifs lobjet an de permettre un autre thread de changer ltat de lobjet. Ce dernier doit signaler le changement avec la mthode Pulse(). Voici un petit scnario expliquant ceci dans les dtails : Le thread T1 possdant laccs exclusif lobjet OBJ, appelle la mthode Wait(OBJ) an de senregistrer dans une liste dattente passive de OBJ. Par cet appel T1 perd laccs exclusif OBJ. Ainsi un autre thread T2 prend laccs exclusif OBJ en appelant la mthode Enter(OBJ).

CS_doNet/05 Page 136 Mercredi, 14. mai 2003 10:32 10

136

Chapitre 5 - Processus, threads et gestion de la synchronisation

T2 modie ventuellement ltat de OBJ puis appelle Pulse(OBJ) pour signaler cette modication. Cet appel provoque le passage du premier thread de la liste dattente passive de OBJ (en loccurrence T1) en haut de la liste dattente active de OBJ. Le premier thread de la liste active de OBJ a la garantie quil sera le prochain avoir les droits daccs exclusifs OBJ ds quils seront librs. Il pourra ainsi sortir de son attente dans la mthode Wait(OBJ). Dans notre scnario T2 libre les droits daccs exclusif sur OBJ en appelant Exit(OBJ) et T1 les rcupre et sort de la mthode Wait(OBJ). La mthode PulseAll() fait en sorte que les threads de la liste dattente passive, passent tous dans la liste dattente active. Limportant est que les threads soient dbloqus dans le mme ordre quils ont appels Wait(). Si Wait(OBJ) est appele par un thread ayant appel plusieurs fois Enter(OBJ), ce thread devra appeler Exit(OBJ) le mme nombre de fois pour librer les droits daccs OBJ. Mme dans ce cas, un seul appel Pulse(OBJ) par un autre thread suft dbloquer le premier thread.

Le programme suivant rsume le scnario expos, la diffrence quil est oblig de tenir compte du fait que T2 peut potentiellement acqurir les droits daccs avant T1. Pour pallier cela on se sert dun boolen qui signale si T1 a dj eu les droits daccs. T1 prend les droits daccs, change le boolen, et Pulse T2 qui est peut tre en train dattendre que T1 ait acquis les droits daccs. Exemple 5-8.
using System; using System.Threading; public class ClasseDeOBJ { public int MyState; public override string ToString() { return MyState.ToString();} } public class Prog { static ClasseDeOBJ OBJ; static bool bT1AAcquisLesDroitsExcusifsSurOBJ = false; public static void Main(string[] args) { OBJ = new ClasseDeOBJ(); Thread T1 = new Thread(new ThreadStart(T1Proc)); Thread T2 = new Thread(new ThreadStart(T2Proc)); T2.Start(); T1.Start(); T1.Join(); T2.Join(); }

CS_doNet/05 Page 137 Mercredi, 14. mai 2003 10:32 10

Les objets de synchronisation

137

static void T1Proc() { Console.WriteLine("T1: Hello!"); try { Monitor.Enter(OBJ); Console.WriteLine("T1: J'ai acquis les drois d'accs sur OBJ"); bT1AAcquisLesDroitsExcusifsSurOBJ = true; Monitor.Pulse(OBJ);// signale T2 que jai obtenu les drois sur OBJ Console.WriteLine("T1: OBJ vaut {0} j'attends que son tat change",OBJ); Monitor.Wait(OBJ);// attend que T2 ait chang ltat de OBJ Console.WriteLine("T1: OBJ vaut {0}",OBJ); } finally { Monitor.Exit(OBJ); } Console.WriteLine("T1: Bye!"); } static void T2Proc() { Console.WriteLine("T2: Hello!"); try { Monitor.Enter(OBJ); Console.WriteLine("T2: J'ai acquis les drois d'accs exclusifs sur OBJ"); if( !bT1AAcquisLesDroitsExcusifsSurOBJ ) { Console.WriteLine("T2: T1 n'a pas encore eu l'accs OBJ, j'attend..."); Monitor.Wait(OBJ);// attend que T1 ait obtenu les drois sur OBJ } OBJ.MyState = 1; Console.WriteLine("T2: J'ai modifi OBJ et je le signale avec Pulse()."); Monitor.Pulse(OBJ);// signale T1 que OBJ chang dtat } finally { Console.WriteLine("T2: Je vais librer les drois d'accs sur OBJ."); Monitor.Exit(OBJ); } Console.WriteLine("T2: Bye!"); } }

CS_doNet/05 Page 138 Mercredi, 14. mai 2003 10:32 10

138

Chapitre 5 - Processus, threads et gestion de la synchronisation

Si T1 obtient les droits daccs exclusifs sur OBJ avant T2 le programme afche :
T1: T1: T1: T2: T2: T2: T2: T1: T1: T2: Hello! J'ai acquis les drois d'accs sur OBJ OBJ vaut 0 j'attends que son tat change... Hello! J'ai acquis les drois d'accs exclusifs sur OBJ J'ai modifi OBJ et je le signale avec Pulse(). Je vais librer les drois d'accs sur OBJ. OBJ vaut 1 Bye! Bye!

Si T2 obtient les droits daccs exclusifs sur OBJ avant T1 le programme afche :
T2: T2: T2: T1: T1: T1: T2: T2: T2: T1: T1: Hello! J'ai acquis les drois d'accs exclusifs sur OBJ T1 n'a pas encore eu l'accs OBJ, j'attend... Hello! J'ai acquis les drois d'accs sur OBJ OBJ vaut 0 j'attends que son tat change... J'ai modifi OBJ et je le signale avec Pulse(). Je vais librer les drois d'accs sur OBJ. Bye! OBJ vaut 1 Bye!

Mutex et vnements
La classe de base abstraite System.Threading.WaitHandle admet trois classes drives, dont lutilisation est bien connue de ceux qui ont dj utilis la synchronisation sous win32 : La classe Mutex (le mot mutex est la concatnation de mutuelle exclusion. En franais on parle parfois de mutant)). La classe AutoResetEvent qui dnit un vnement repositionnement automatique. La classe ManualResetEvent qui dnit un vnement repositionnement manuelle.

La classe WaitHandle et ses classes drives, ont la particularit dimplmenter la mthode non statique WaitOne() et les mthodes statiques WaitAll() et WaitAny(). Elles permettent respectivement dattendre quun objet soit signal, que tous les objets dans un tableau soient signals, quau moins un objet dans un tableau soit signal. Contrairement la classe Monitor et Interlocked, ces classes doivent tre instancies pour tre utilises. Il faut donc raisonner ici en terme dobjets de synchronisation et non dobjets synchroniss. Ceci implique que les objets passs en paramtre des mthodes statiques WaitAll() et WaitAny() sont soit des mutex, soit des vnements. Il est important de noter une autre grosse distinction entre lutilisation des classes drives de WaitHandle et lutilisation de la classe Monitor. Lutilisation de la classe Monitor est totalement gre par le CLR et se cantonne un seul processus. Lutilisation

CS_doNet/05 Page 139 Mercredi, 14. mai 2003 10:32 10

Les objets de synchronisation

139

des classes drives de WaitHandle fait appel (dans la version actuelle 1.0 et 1.1 de .NET) des objets win32 non grs. De plus, un mme mutex peut tre connu de plusieurs processus de la mme machine. Lutilisation de tels objets est donc plus coteuse.

Les Mutex
En terme de fonctionnalits les mutex sont proches de lutilisation de la classe Monitor ces diffrences prs : En nommant un mutex, on peut lutiliser dans plusieurs processus dune mme machine. Lutilisation de Monitor ne permet pas de se mettre en attente sur plusieurs objets. Les mutex nont pas la fonctionnalit des mthodes Wait(), Pulse() et PulseAll() de la classe Monitor.

Le programme suivant montre lutilisation dun mutex nomm pour protger laccs une ressource partage par plusieurs processus sur la mme machine. La ressource partage est un chier o chaque programme crit 10 lignes. Exemple 5-9.
using System; using System.Threading; using System.IO; class Prog { static void Main(string[] args) { // Le mutex est nomm MutexTest Mutex MutexFile = new Mutex(false,"MutexTest"); for(int i = 0; i<10 ;i++) { MutexFile.WaitOne(); // Ouvre le fichier, crit Hello i, ferme le fichier FileInfo FI = new FileInfo("tmp.txt"); StreamWriter SW = FI.AppendText(); SW.WriteLine("Hello {0}",i); SW.Flush(); SW.Close(); // attend 1 seconde // pour rendre vidente l'action du mutex Console.WriteLine("Hello {0}",i); Thread.Sleep(1000); MutexFile.ReleaseMutex(); } MutexFile.Close(); } }

CS_doNet/05 Page 140 Mercredi, 14. mai 2003 10:32 10

140

Chapitre 5 - Processus, threads et gestion de la synchronisation

Notez bien lutilisation de la mthode WaitOne() qui bloque le thread courant jusqu lobtention du mutex et lutilisation de la mthode ReleaseMutex() qui libre le mutex. Notez que dans ce programme, new Mutex ne signie pas forcment la cration du mutex mais la cration dune rfrence vers le mutex nomm "MutexTest". Le mutex est effectivement cr par le systme dexploitation seulement sil nexiste pas dj. De mme la mthode Close() ne dtruit pas forcment le mutex si ce dernier est encore rfrenc par dautres processus. Pour ceux qui avaient lhabitude dutiliser la technique du mutex nomm en win32, pour viter de lancer deux instances du mme programme sur la mme machine, sachez quil existe une meilleure faon de procder sous .NET, dcrite dans la section page 120.

Les vnements
Contrairement aux mcanismes de synchronisation vus jusquici, les vnements ne dnissent pas explicitement de notion dappartenance dune ressource un thread un instant donn. Les vnements servent passer une notication dun thread lautre, cette notication tant un vnement sest pass. Lvnement concern est associ une instance dune des deux classes dvnement, System.Threading.AutoResetEvent et System.Threading.ManualResetEvent. Concrtement un thread attend quun vnement soit signal en appelant la fonction bloquante WaitOne() sur lobjet vnement associ. Puis un thread signale lvnement en appelant la mthode Set() sur lobjet vnement associ et permet ainsi au premier thread de reprendre son excution. La diffrence entre les vnements AutoResetEvent (vnement repositionnement automatique) et ManualResetEvent (vnement repositionnement manuel) est que lon a besoin dappeler la mthode Reset() pour repositionner lvnement en position non active, sur un vnement de type repositionnement manuel aprs lavoir signal. La diffrence entre le repositionnement manuel et automatique est plus importante que lon pourrait croire. Si plusieurs threads attendent sur un mme vnement repositionnement automatique, il faut signaler lvnement une fois pour chaque thread. Dans le cas dun vnement repositionnement manuel il suft de signaler une fois lvnement pour dbloquer tous les threads. Le programme suivant cre deux threads, T0 et T1, qui incrmentent chacun leur propre compteur des vitesses diffrentes. T0 signale lvnement EV[0] lorsquil est arriv 5 et T1 signale lvnement EV[1] lorsquil est arriv 8. Le thread principal attend que les deux vnements soient signals pour afcher un message.

CS_doNet/05 Page 141 Mercredi, 14. mai 2003 10:32 10

Les objets de synchronisation


Exemple 5-10.
using System; using System.Threading; class Prog { static AutoResetEvent[] EV; static void Main(string[] args) { EV = new AutoResetEvent[2]; // position initiale de lvnement: false EV[0] = new AutoResetEvent(false); EV[1] = new AutoResetEvent(false); Thread T0 = new Thread(new ThreadStart(ThreadProc0)); Thread T1 = new Thread(new ThreadStart(ThreadProc1)); T0.Start(); T1.Start(); AutoResetEvent.WaitAll(EV); Console.WriteLine("MainThread: Thread0 est arriv 5"+ "et Thread1 est arriv 8"); T0.Join(); T1.Join(); } static void ThreadProc0() { for(int i=0;i<10;i++) { Console.WriteLine("Thread0: {0}",i); if( i == 5 )EV[0].Set(); Thread.Sleep(100); } } static void ThreadProc1() { for(int i=0;i<10;i++) { Console.WriteLine("Thread1: {0}",i); if( i == 8 )EV[1].Set(); Thread.Sleep(60); } } }

141

Ce programme afche :
Thread0: Thread1: Thread1: Thread0: Thread1: Thread1: Thread0: 0 0 1 1 2 3 2

CS_doNet/05 Page 142 Mercredi, 14. mai 2003 10:32 10

142

Chapitre 5 - Processus, threads et gestion de la synchronisation

Thread1: 4 Thread0: 3 Thread1: 5 Thread1: 6 Thread0: 4 Thread1: 7 Thread1: 8 Thread0: 5 MainThread: Thread0 est arriv 5 et Thread1 est arriv 8 Thread1: 9 Thread0: 6 Thread0: 7 Thread0: 8 Thread0: 9

La classe System.Threading.ReaderWriterLock
La classe System.Threading.ReaderWriterLock implmente le mcanisme de synchronisation accs lecture multiple/accs criture unique. Contrairement au modle de synchronisation accs exclusif offert par la classe Monitor ou Mutex, ce mcanisme tient compte du fait quun thread demande les droits daccs en lecture ou en criture. Un accs en lecture peut tre obtenu lorsquil ny a pas daccs en criture courant. Un accs en criture peut tre obtenu lorsquil ny a aucun accs courant, ni en lecture ni en criture. De plus lorsquun accs en criture a t demand mais pas encore obtenu, les nouvelles demandes daccs en lecture sont mises en attente. Lorsque ce modle de synchronisation peut tre appliqu, il faut toujours le privilgier par rapport au modle propos par les classes Monitor ou Mutex. En effet, le modle accs exclusif ne permet en aucun cas des accs simultans et est donc moins performant. De plus, empiriquement, on se rend compte que la plupart des applications accdent beaucoup plus souvent aux donnes en lecture quen criture. A linstar des mutex et des vnements et au contraire de la classe Monitor, la classe ReaderWriterLock doit tre instancie pour tre utilise. Il faut donc ici aussi raisonner en terme dobjets de synchronisation et non dobjets synchroniss. Voici un exemple de code qui montre lutilisation de cette classe, mais qui nen exploite pas toutes les possibilits. En effet les mthodes DowngradeFromWriterLock() et UpgradeToWriterLock() (non dtailles ici, voir les MSDN) permettent de demander un changement de droit daccs sans avoir librer son droit daccs courant.

CS_doNet/05 Page 143 Mercredi, 14. mai 2003 10:32 10

Les objets de synchronisation


Exemple 5-11.
using System; using System.Threading; class Prog { static int LaRessource = 0; static ReaderWriterLock RWL; static void Main(string[] args) { RWL = new ReaderWriterLock(); Thread TR0 = new Thread(new ThreadStart(ThreadReader)); Thread TR1 = new Thread(new ThreadStart(ThreadReader)); Thread TW = new Thread(new ThreadStart(ThreadWriter)); TR0.Start(); TR1.Start(); TW.Start(); TR1.Join(); TR1.Join(); TW.Join(); } static void ThreadReader() { for(int i=0;i<5;i++) { RWL.AcquireReaderLock(1000); Console.WriteLine( "Dbut lecture LaRessource={0}",LaRessource); Thread.Sleep(5); Console.WriteLine( "Fin lecture LaRessource={0}",LaRessource); RWL.ReleaseReaderLock(); } } static void ThreadWriter() { for(int i=0;i<5;i++) { RWL.AcquireWriterLock(1000); Console.WriteLine( "Dbut criture LaRessource={0}",LaRessource); Thread.Sleep(100); LaRessource++; Console.WriteLine( "Fin criture LaRessource={0}",LaRessource); RWL.ReleaseWriterLock(); } } }

143

CS_doNet/05 Page 144 Mercredi, 14. mai 2003 10:32 10

144
Ce programme afche :
Dbut Dbut Fin Fin Dbut Fin Dbut Dbut Fin Fin Dbut Fin Dbut Dbut Fin Fin Dbut Fin Dbut Dbut Fin Fin Dbut Fin Dbut Dbut Fin Fin Dbut Fin lecture lecture lecture lecture criture criture lecture lecture lecture lecture criture criture lecture lecture lecture lecture criture criture lecture lecture lecture lecture criture criture lecture lecture lecture lecture criture criture

Chapitre 5 - Processus, threads et gestion de la synchronisation

LaRessource=0 LaRessource=0 LaRessource=0 LaRessource=0 LaRessource=0 LaRessource=1 LaRessource=1 LaRessource=1 LaRessource=1 LaRessource=1 LaRessource=1 LaRessource=2 LaRessource=2 LaRessource=2 LaRessource=2 LaRessource=2 LaRessource=2 LaRessource=3 LaRessource=3 LaRessource=3 LaRessource=3 LaRessource=3 LaRessource=3 LaRessource=4 LaRessource=4 LaRessource=4 LaRessource=4 LaRessource=4 LaRessource=4 LaRessource=5

Accs synchroniss aux lments dune collection


Les classes suivantes, reprsentant un type de collection, prsentent la fonctionnalit davoir ventuellement un accs synchronis leurs lments :
Sytem.Collections.ArrayList Sytem.Collections.Queue Sytem.Collections.Stack Sytem.Collections.SortedList Sytem.Collections.Hashtable

Les classes suivantes ne prsentent pas daccs synchronis leurs lments :


System.Array System.Collection.BitArray

Notez que la prsentation des collections fait lobjet du chapitre 14, page 431.

CS_doNet/05 Page 145 Mercredi, 14. mai 2003 10:32 10

Les objets de synchronisation

145

Cette fonctionnalit est possible grce la mthode Synchronized() qui retourne un wrapper daccs synchronis de la collection. Ce wrapper est de la mme classe que la collection. Aussi, vous pouvez diffrencier une collection synchronise dune collection non synchronise grce la proprit, accessible en lecture seule, bool IsSynchronized() de linterface System.Collections.ICollection. Cette interface est implmente par toutes les classes de collection. Voici un exemple pour illustrer tout ceci : Exemple 5-12.
using System.Collections; public class Prog { public static void Main() { ArrayList Liste1 = new ArrayList(); Liste1.Add( 1 ); Liste1.Add( 2 ); ArrayList Liste2 = ArrayList.Synchronized(Liste1); bool b1 = Liste1.IsSynchronized; bool b2 = Liste2.IsSynchronized; // b1 vaut false et b2 vaut true, // Liste2 est un wrapper synchronis de Liste1 } }

En terminologie design pattern, on dit que lon dcore la collection. Cela signie que lon ajoute des fonctionnalits un objet (en loccurrence laccs synchronis une collection) en interceptant les appels laide dobjets prsentant les mme mthodes.

Autre faon pour synchroniser une collection


Le parcours des lments dun tableau ne peut pas tre synchronis par lutilisation dun wrapper. En effet, le wrapper synchronise les accs et non pas les ensembles daccs. Par exemple supposons que le thread T1 parcourt une collection et que le thread T2 modie la collection en mme temps. Il y a de fortes chances pour que le parcours de T1 soit perturb, ce qui mne en gnral au lancement dune exception. Pour se prmunir de cela, vous pouvez synchroniser un ensemble daccs une collection en utilisant la proprit, accessible en lecture seule, object SyncRoot() de linterface ICollection. Cette interface est implmente par toutes les classes de collection. La mthode SyncRoot() retourne un objet utilisable par un objet de synchronisation Monitor (cest-dire le mot-cl du langage C# lock). On peut ainsi synchroniser avec cette technique les accs aux collections qui ne prsentent pas la mthode Synchronized(). Lexemple suivant synchronise lnumration des lments dun tableau dentier :

CS_doNet/05 Page 146 Mercredi, 14. mai 2003 10:32 10

146
Exemple 5-13.
using System;

Chapitre 5 - Processus, threads et gestion de la synchronisation

public class Prog { public static void Main() { int [] Tab = {1,2,3}; lock( Tab.SyncRoot ) { foreach( int i in Tab ) { // faire un traitement... } } } }

Lattribut System.Runtime.Remoting.Contexts. SynchronizationAttribute


Lorsque lattribut System.Runtime.Remoting.Contexts.Synchronization est appliqu une classe, une instance de cette classe ne peut pas tre accde par plus dun thread la fois. Pour que ce comportement sapplique correctement il faut que la classe sur laquelle sapplique lattribut soit context-bound, cest-dire quelle doit driver de la classe System.ContextBoundObject. La signication du terme context-bound est explique page 692. Voici un exemple illustrant comment appliquer ce comportement : Exemple 5-14.
using System; using System.Runtime.Remoting.Contexts; using System.Threading; [Synchronization(SynchronizationAttribute.REQUIRED)] public class UneClasse : ContextBoundObject { public void AfficheThreadHashVal() { Console.WriteLine("Dbut: ThreadHashVal= " + Thread.CurrentThread.GetHashCode() ); Thread.Sleep(1000); Console.WriteLine("Fin: ThreadHashVal= " + Thread.CurrentThread.GetHashCode() );

CS_doNet/05 Page 147 Mercredi, 14. mai 2003 10:32 10

Gestion dun pool de threads

147

} } public class Prog { static UneClasse m_Objet; static void Main() { m_Objet = new UneClasse(); Thread T0 = new Thread(new ThreadStart(ThreadProc)); Thread T1 = new Thread(new ThreadStart(ThreadProc)); T0.Start(); T1.Start(); T0.Join(); T1.Join(); } static void ThreadProc() { for(int i=0;i<2;i++) m_Objet.AfficheThreadHashVal(); } }

Cet exemple afche :


Dbut: Fin: Dbut: Fin: Dbut: Fin: Dbut: Fin: ThreadHashVal= ThreadHashVal= ThreadHashVal= ThreadHashVal= ThreadHashVal= ThreadHashVal= ThreadHashVal= ThreadHashVal= 27 27 28 28 27 27 28 28

Notez que le Framework .NET prsente un autre attribut ayant ce nom mais faisant partie dune autre espace de noms. Cet attribut System.EntrepriseServices.Synchronization a la mme nalit, mais il utilise le service dentreprise COM+ de synchronisation. Cependant lutilisation de lattribut System.Runtime.Remoting.Contexts.Synchronization est prfrable pour deux raisons : Son utilisation est plus performante. Ce mcanisme supporte les appels asynchrones, contrairement la version COM+.

La notion de service dentreprise COM+ est prsente page 595.

Gestion dun pool de threads


Introduction
Le concept de pool de threads nest pas nouveau. Cependant le Framework .NET vous permet dutiliser un pool de threads beaucoup plus simplement que nimporte quelle autre technologie, grce la classe System.Threading.ThreadPool.

CS_doNet/05 Page 148 Mercredi, 14. mai 2003 10:32 10

148

Chapitre 5 - Processus, threads et gestion de la synchronisation

Dans une application multithreads, la plupart des threads passent leur temps attendre des vnements. Concrtement vos threads sont globalement sous exploits. De plus, le fait de devoir tenir compte de la gestion des threads lors du design de votre application est une difcult dont on se passerait volontiers. Lutilisation dun pool de threads rsout de faon lgante et performante ces problmes. Vous postez des tches au pool qui se charge de les distribuer ces threads. Le pool est entirement responsable de : la cration et de la destruction de ses threads ; la distribution des tches ; lutilisation optimale de ses threads.

Le dveloppeur est donc dcharg de ces responsabilits. Malgr tous ces avantages, il est souhaitable de ne pas utiliser de pool de threads lorsque : Vos tches doivent tre gres avec un systme de priorit ; Vos tches sont longues sexcuter (plusieurs secondes) ; Vos tches doivent tre traites dans des STA (Single Apartment Thread). En effet les threads dun pool sont de type MTA (Multiple Apartement Thread). Cette notion de thread apartment, inhrente la technologie COM, est explique page 238.

Utilisation dun pool de threads


En .NET il ny a quun pool de threads par processus. Ainsi toutes les mthodes de la classe ThreadPool sont statiques puisquelles sappliquent lunique pool. Le Framework .NET utilise ce pool pour les appels asynchrones, dcrit un peu plus loin page 151, les mcanismes dentr/sortie asynchrones (aussi appels completion ports en anglais, ils sont dcrit page 489 et page 496) ou les timers. Le nombre maximal de threads du pool est de 25 threads par processeur pour traiter les oprations asynchrones et de 25 threads ouvrier (worker thread en anglais) par processeur. Ces deux limites par dfaut sont modiables tout moment en appelant la mthode CorSetMaxThreads de linterface COM ICorThreadpool dnie dans la DLL cale mscoree.dll. Pour accder cette fonctionnalit, il faut fabriquer son propre hte du moteur dexcution, comme expliqu page 81. Si le nombre maximal de threads est atteint dans le pool, ce dernier ne cre plus de nouveaux threads et les tches dans la le du pool ne seront traites que lorsquun thread du pool se librera. En revanche, le thread responsable de la cration de la tche, na pas attendre quelle soit traite. Vous pouvez utiliser le pool de threads de deux faons diffrentes : En postant vos propres tches et leurs mthodes de traitement avec la mthode QueueUserWorkItem(). Une fois quune tche a t poste au pool, elle ne peut plus tre annule. En crant un timer qui poste priodiquement une tche prdnie et sa mthode de traitement au pool. Pour cela, il faut utiliser la mthode RegisterWaitForSingleObject().

CS_doNet/05 Page 149 Mercredi, 14. mai 2003 10:32 10

Gestion dun pool de threads

149

Notez que chacune de ces deux mthodes existe dans une version non protge (UnsafeQueueUserWorkItem() et UnsafeRegisterWaitForSingleObject()) . Ces versions non protges permettent aux threads ouvriers du pool de ne pas tre dans le mme contexte de scurit que le thread qui a dpos la tche. De plus lutilisation de ces mthodes amliore les performances puisque la pile du thread qui a dpos la tche nest pas vrie lors de la gestion des contextes de scurit. Notez que les mthodes de traitement sont rfrences par des dlgus. Cette notion de dlgu est prsente page 401. Voici un exemple qui montre lutilisation de tches utilisateurs (paramtres par un numro et traites par la mthode ThreadTache()) et de tches postes priodiquement (sans paramtre et traites par la mthode ThreadTacheWait()). Les tches utilisateurs sont volontairement longues pour forcer le pool crer de nouveaux threads. Lorsque lon utilise un pool de threads, il vaut mieux ne pas pouvoir diffrencier les threads du pool. Cependant, pour les besoins de lexemple les threads du pool sont nomms leur cration dans la mthode NommeEventuellementCeThread() pour pouvoir observer quel thread excute quelle tche : Exemple 5-15.
using System; using System.Threading; public class ThreadPoolTest { public static int Main(string[] args) { // Positionnement initial de lvnement: false AutoResetEvent ev = new AutoResetEvent(false); ThreadPool.RegisterWaitForSingleObject( ev, // Mthode de traitement de la tche priodique new WaitOrTimerCallback(ThreadTacheWait), null, // la tche priodique n'a pas de paramtre 2000, // la priode est de 2 secondes false ); // vous avez la possibilit de poster une tche priodique // en appelant 'ev.Set()' // Poste 5 tches utilisateur de paramtres 1,2,3,4,5 for(int count = 0; count < 5; ++count) ThreadPool.QueueUserWorkItem( new WaitCallback(ThreadTache), count); // Attente de 20 secondes avant de finir le processus // car les threads du pool sont des threads background Thread.Sleep(20000); return 0; }

CS_doNet/05 Page 150 Mercredi, 14. mai 2003 10:32 10

150

Chapitre 5 - Processus, threads et gestion de la synchronisation

static void ThreadTache(Object Obj) { NommeEventuellementCeThread(); Console.WriteLine("{0} Tche#{1} Dbut", Thread.CurrentThread.Name,Obj.ToString()); Thread.Sleep(5000); Console.WriteLine("{0} Tche#{1} Fin", Thread.CurrentThread.Name,Obj.ToString()); } static void ThreadTacheWait(object Obj, bool signaled) { NommeEventuellementCeThread(); Console.WriteLine("{0} TcheWait", Thread.CurrentThread.Name); } static int ThreadNumber = 0; static void NommeEventuellementCeThread() { if( Thread.CurrentThread.Name == null ) { Thread.CurrentThread.Name = "Thread#"+ThreadNumber; // Synchronise lincrmentation de ThreadNumber Interlocked.Increment( ref ThreadNumber); } } }

Ce programme afche ceci (dune manire non dterministe) :


Thread#0 Thread#1 Thread#2 Thread#0 Thread#0 Thread#1 Thread#1 Thread#2 Thread#2 Thread#2 Thread#2 Thread#2 Thread#2 Thread#0 Thread#1 Thread#2 Thread#0 Thread#0 Thread#0 Press any Tche#0 Dbut Tche#1 Dbut Tche#2 Dbut Tche#0 Fin Tche#3 Dbut Tche#1 Fin Tche#4 Dbut Tche#2 Fin TcheWait TcheWait TcheWait TcheWait TcheWait Tche#3 Fin Tche#4 Fin TcheWait TcheWait TcheWait TcheWait key to continue

CS_doNet/05 Page 151 Mercredi, 14. mai 2003 10:32 10

Appel asynchrone dune mthode

151

Appel asynchrone dune mthode


Pour aborder cette section, il est ncessaire davoir compris la notion de dlgu, explique page 401.

On dit dun appel de mthode quil est synchrone lorsque le thread du cot qui ralise lappel, attend que la mthode soit excute avant de continuer. Ce comportement consomme des ressources puisque le thread est bloqu pendant ce temps. Lors dun appel sur un objet distant, cette dure est potentiellement immense, puisque le cot dun appel rseau reprsente des milliers, voire des millions, de cycles processeurs. Cependant cette attente nest obligatoire que dans le cas o les informations retournes par lappel de la mthode sont immdiatement consommes aprs lappel. Dans la programmation en gnral et dans les architectures distribues en particulier, il arrive souvent quun appel de mthode effectue une action et ne retourne que linformation dcrivant si laction sest bien passe ou non. Dans ce cas, le programme na pas forcment besoin de savoir immdiatement si laction sest bien passe. On peut dcider dessayer de recommencer cette action plus tard si on apprend quelle a chou. Pour grer ce type de situation on peut utiliser un appel asynchrone. Lide est que le thread qui ralise un appel de mthode sur un objet, retourne immdiatement, sans attendre la n de lappel. Lappel est automatiquement pris en charge par un thread du pool de threads du processus. Le programme peut ultrieurement rcuprer les informations retournes par lappel asynchrone. La technique dappel asynchrone est entirement gre par le CLR. Le mcanisme que nous dcrivons peut tre utilis dans votre propre architecture. Il est aussi utilis par les classes du Framework .NET, notamment pour grer les ots de donnes dune manire asynchrone ou pour grer des appels asynchrones sur des objets distants, cest--dire situs dans un autre domaine dapplication.

Dlgation asynchrone
Lors dun appel asynchrone vous navez pas crer ni vous occupez du thread qui excute le corps de la mthode. Ce thread est gr par le pool de threads dcrit un peu plus haut. Avant dutiliser effectivement un dlgu asynchrone, il est judicieux de remarquer que lorsque vous compilez le code de lExemple 5-16, la classe dlgue DeuxNombresProc implmente les mthodes BeginInvoke() et EndInvoke(), gnres par le compilateur. Pour sen convaincre, la reprsente lanalyse de lassemblage cr avec ildasm.exe. Je fais cette remarque car le mcanisme dintellisense de Visual Studio .NET ne connat pas ces mthodes : elles sont produites la compilation.

CS_doNet/05 Page 152 Mercredi, 14. mai 2003 10:32 10

152
Exemple 5-16.
using System; using System.Threading;

Chapitre 5 - Processus, threads et gestion de la synchronisation

class Prog { public delegate int DeuxNombresProc(int a,int b); static int WriteSomme(int a,int b) { Console.WriteLine( "Somme = {0}" ,a+b); return a+b; } static void Main(string[] args) { DeuxNombresProc Proc = new DeuxNombresProc(WriteSomme); Proc(10,10) ; } }

Figure 5-4. Les mthodes BeginInvoke() et EndInvoke() dun dlgu

Pour appeler une mthode dune manire asynchrone, il suft de rfrencer la mthode dans un dlgu avec la mme signature que la mthode, et dappeler la mthode BeginInvoke() sur ce dlgu. Comme vous lavez remarqu, le compilateur a fait en sorte que les premiers arguments de BeginInvoke() soient les arguments de la mthode appeler. Les deux derniers arguments de cette mthode font lobjet des prochaines sections. La valeur de retour de lappel asynchrone dune mthode, peut tre rcupre en appelant la mthode EndInvoke(). L aussi, le compilateur a fait en sorte que le type de la valeur de retour de EndInvoke() soit le mme que le type de la valeur de retour de la dlgation (ce type est int dans notre exemple). Lappel EndInvoke() est bloquant. Cest--dire que lappel ne retourne que lorsque lexcution asynchrone est effectivement termine. Le code de lexemple prcdent a t modi, de faon que la mthode WriteSomme() soit appele dune manire asynchrone. Notez que pour bien diffrencier le thread excutant la mthode Main() et le thread excutant la mthode WriteSomme(), nous afchons la valeur de hachage du thread courant (qui est diffrente pour chaque thread).

CS_doNet/05 Page 153 Mercredi, 14. mai 2003 10:32 10

Appel asynchrone dune mthode


Exemple 5-17.
using System; using System.Threading; class Prog { public delegate int DeuxNombresProc(int a,int b); static int WriteSomme(int a,int b) { int Somme = a+b; Console.WriteLine("Thread {0}:WriteSomme() Somme = {1}", Thread.CurrentThread.GetHashCode(),Somme); return Somme; } static void Main(string[] args) { DeuxNombresProc Proc = new DeuxNombresProc(WriteSomme); IAsyncResult Async = Proc.BeginInvoke(10,10,null,null); int Somme = Proc.EndInvoke(Async); Console.WriteLine("Thread {0}:Main() Somme = {1}", Thread.CurrentThread.GetHashCode(),Somme); } }

153

Sur ma machine, ce programme afche :


Thread 15:WriteSomme() Somme = 20 Thread 18:Main() Somme = 20

Un appel asynchrone est matrialis par un objet dont la classe implmente linterface System.IAsyncResult. Dans cet exemple la classe sous-jacente est System.Runtime.Remoting.Messaging.AsyncResult. Lobjet AsyncResult est retourn par la mthode BeginInvoke(). Il est pass en argument de la mthode EndInvoke() pour identier lappel asynchrone. Notez que si une exception est lance lors dun appel asynchrone, elle est automatiquement intercepte et stocke par le CLR. Le CLR relancera lexception lors de lappel EndInvoke().

Procdure de nalisation
Vous avez la possibilit de spcier une mthode qui sera automatiquement appele lorsque lappel asynchrone sera termin. Cette mthode est appele procdure de finalisation. Une procdure de nalisation est appele par le mme thread que celui qui a excut lappel asynchrone. Pour utiliser une procdure de nalisation, il vous suft de spcier la mthode dans une dlgation de type System.AsyncCallback comme avant-dernier paramtre de la mthode BeginInvoke(). Cette mthode doit tre conforme cette dlgation, cest--dire quelle doit retourner le type void et prendre pour seul argument une interface IAsyncResult. Comme le montre lexemple suivant, cette mthode doit appeler EndInvoke().

CS_doNet/05 Page 154 Mercredi, 14. mai 2003 10:32 10

154

Chapitre 5 - Processus, threads et gestion de la synchronisation

Un problme se pose, car les threads du pool utiliss pour traiter les appels asynchrones sont des threads background. A linstar de lexemple ci-dessous, il faut que vous implmentiez un mcanisme de gestion dvnements pour vous assurer que lapplication ne se termine pas sans avoir termin lexcution asynchrone. Notez que linterface IAsyncResult prsente un objet de synchronisation dune classe drive de WaitHandle, mais cet objet est signal ds que le traitement asynchrone est ni et avant que la procdure de nalisation soit appele. Cet objet ne peut donc pas permettre dattendre la n de lexcution de la procdure de nalisation. Exemple 5-18.
using System; using System.Threading; using System.Runtime.Remoting.Messaging; class Prog { public delegate int DeuxNombresProc(int a,int b); // position initiale de lvnement : false static AutoResetEvent E = new AutoResetEvent(false); static int WriteSomme(int a,int b) { Console.WriteLine( "{0}: Somme = {1}",Thread.CurrentThread.GetHashCode(),a+b); return a+b; } static void SommeFinie(IAsyncResult Async) { // attend une seconde pour simuler un traitement long Thread.Sleep(1000); DeuxNombresProc Proc = (DeuxNombresProc) ((AsyncResult)Async).AsyncDelegate; int Somme = Proc.EndInvoke(Async); Console.WriteLine( "{0}: Procdure de finalisation Somme = {1}", Thread.CurrentThread.GetHashCode(),Somme); E.Set(); } static void Main(string[] args) { DeuxNombresProc Proc = new DeuxNombresProc(WriteSomme); IAsyncResult Async = Proc.BeginInvoke(10,10, new AsyncCallback(SommeFinie),null); Console.WriteLine( "{0}: BeginInvoke() appele! Attend l'xcution de SommeFinie() ", Thread.CurrentThread.GetHashCode());

CS_doNet/05 Page 155 Mercredi, 14. mai 2003 10:32 10

Appel asynchrone dune mthode

155

E.WaitOne(); Console.WriteLine( "{0}: Bye... ",Thread.CurrentThread.GetHashCode()); } }

Cet exemple afche :


12: 14: 14: 12: BeginInvoke() appele! Attend l'xcution de SommeFinie() Somme = 20 Procdure de finalisation Somme = 20 Bye...

Si vous enlevez le mcanisme dvnement, cet exemple afche ceci :


12: BeginInvoke() appele! Attend l'xcution de SommeFinie() 12: Bye...

Lapplication nattend pas la n de lexcution du traitement asynchrone et de sa procdure de nalisation.

Passage dun tat la procdure de nalisation


Si vous ne le positionnez pas null, le dernier paramtre de la mthode BeginInvoke() reprsente une rfrence vers un objet utilisable la fois dans le thread qui dclenche lappel asynchrone et dans la procdure de nalisation. Une autre rfrence vers cet objet est la proprit AsyncState de linterface IAsyncResult. Vous pouvez vous en servir pour reprsenter un tat, positionn dans la procdure de nalisation. Par exemple, lvnement de lexemple de la section prcdente peut tre vu comme un tat. Voici le code rcrit pour utiliser cette fonctionnalit : Exemple 5-19.
using System; using System.Threading; using System.Runtime.Remoting.Messaging; class Prog { public delegate int DeuxNombresProc(int a,int b); static int WriteSomme(int a,int b) { int Somme = a+b; Console.WriteLine( "{0}: Somme = {1}",Thread.CurrentThread.GetHashCode(),Somme); return Somme; } static void SommeFinie(IAsyncResult Async) {

CS_doNet/05 Page 156 Mercredi, 14. mai 2003 10:32 10

156

Chapitre 5 - Processus, threads et gestion de la synchronisation

// Attend une seconde pour simuler un traitement long. Thread.Sleep(1000); DeuxNombresProc Proc = (DeuxNombresProc) ((AsyncResult)Async).AsyncDelegate; int Somme = Proc.EndInvoke(Async); Console.WriteLine( "{0}: Procdure de finalisation Somme = {1}", Thread.CurrentThread.GetHashCode(),Somme); ((AutoResetEvent)Async.AsyncState).Set(); } static void Main(string[] args) { DeuxNombresProc Proc = new DeuxNombresProc(WriteSomme); AutoResetEvent E = new AutoResetEvent(false); IAsyncResult Async = Proc.BeginInvoke(10,10, new AsyncCallback(SommeFinie), E ); Console.WriteLine( "{0}: BeginInvoke() appele! Attend l'xcution de SommeFinie() ", Thread.CurrentThread.GetHashCode()); E.WaitOne(); Console.WriteLine( "{0}: Bye... ",Thread.CurrentThread.GetHashCode()); } }

Appels sans retour (One-Way)


Vous avez la possibilit dappliquer lattribut System.Runtime.Remoting.Messaging.OneWay nimporte quelle mthode, statique ou non. Cet attribut indique au CLR que cette mthode ne retourne aucune information. Mme si une mthode qui retourne une valeur de retour ou des arguments de retour (i.e dnis avec le mot-cl out) est marque avec cet attribut, elle ne retourne rien. Une mthode marque avec lattribut OneWay peut tre appele dune manire synchrone ou asynchrone. Si une exception est lance et non rattrape durant lexcution dune mthode marque avec lattribut OneWay, elle est propage si lappel est synchrone. Dans le cas dun appel asynchrone sans retour lexception nest pas propage. Dans la plupart des cas, les mthodes marques sans retour sont appeles de manire asynchrone. Les appels asynchrones sans retour effectuent en gnral des tches annexes dont la russite ou lchec nont pas dincidence sur le bon droulement de lapplication. La plupart du temps, on les utilise pour communiquer des informations sur le droulement de lapplication.

Vous aimerez peut-être aussi