Vous êtes sur la page 1sur 30

Les Threads en .

NET
Par gretro

www.openclassrooms.com

Licence Creative Commons 4 2.0 Dernire mise jour le 24/08/2011

2/31

Sommaire
Sommaire ........................................................................................................................................... 2 Lire aussi ............................................................................................................................................ 1 Les Threads en .NET ......................................................................................................................... 3
Les bases des delegates .................................................................................................................................................. 3
Les delegates, une rfrence en la matire ! .............................................................................................................................................................. 3 Partis pour la gloire ! ................................................................................................................................................................................................... 4

Les threads ........................................................................................................................................................................ 7


Un peu d'histoire ......................................................................................................................................................................................................... 7 Le multi-task en Windows ........................................................................................................................................................................................... 7 Les threads, enfin ! ...................................................................................................................................................................................................... 8 Les mcanismes de synchronisation ........................................................................................................................................................................ 12

Les threads avec les Windows Forms ............................................................................................................................ 24


BackgroundWorker .................................................................................................................................................................................................... 28 Partager ..................................................................................................................................................................................................................... 30

www.openclassrooms.com

Sommaire

3/31

Les Threads en .NET

Par

gretro

Mise jour : 24/08/2011 Difficult : Difficile Il existe en .NET quelques cratures qui se cachent dans le noir. Parmi celles-ci sont les threads, les fonctions lambda et les delegates. N'ayez craintes chers Zros, voici un tutoriel qui rpondra vos attentes en ce qui concerne les threads. Par contre, l'tude de cette discipline requiert galement une bonne connaissance du C#. Nous verrons galement au passage les delegates, rfrences sur des mthodes. Je vous le dis maintenant, ce n'est pas un sujet des plus faciles, mais cela permet de nombreuses amliorations de vos applications, notamment pour leur permettre de communiquer en rseau, mais aussi de rendre fluide certaines oprations lourdes en traitement sans bloquer votre application ! Cours pour zros avertis seulement ! Si vous ne connaissez pas le C#, ce tutoriel n'est pas pour vous.

Si vous dsirez suivre le cours en VB.NET, je vous conseille d'utiliser un traducteur C# -> VB.NET afin de traduire les exemples. Sommaire du tutoriel :

Les bases des delegates Les threads Les threads avec les Windows Forms

Les bases des delegates Les delegates, une rfrence en la matire !


Qu'est-ce qu'un delegate ?
Un delegate est un concept abstrait du C#. Jusqu' maintenant, une variable pouvait contenir de nombreuses choses. On traite, par exemple, les objets comme des variables. Elles permettent aussi de mettre en mmoire des donnes, comme du texte, des nombres entiers ou flottants, des boolens. Ces cas ne sont que des exemples. Un delegate est en fait une variable un peu spciale... Elle ne sert qu' donner une rfrence vers une mthode ou fonction. Elle est donc de type rfrence, comme un objet ! L'utilit sera bien videmment d'envoyer ces delegates en paramtres ! Pensez-y, une mthode gnrique unique pourrait s'occuper de lancer un nombre infini d'oprations dtermines par vos bons soins. Ne vous en faites pas si ce concept est abstrait pour le moment. Comprenez seulement qu'on peut drastiquement augmenter la rutilisation du code avec cet outil. N'estce pas le grand but de la POO que de rutiliser le code ?

Comment on fait un delegate ? a parat compliqu...


Oui, a parat compliqu aux premiers abords, mais a devient vite facile. Allez, on s'y lance !

www.openclassrooms.com

Les Threads en .NET

4/31

Partis pour la gloire !


Je vais commencer par vous donner un exemple bien simple, un cas que vous utilisez toujours lorsque vous programmez en C# ! Code : C# using using using using System ; System.Collections.Generic; System.Linq; System.Text;

namespace TestThread { class Program { static void Main(string[] args) { bool resultat = Test("Ceci est un test qui est ngatif !"); bool res2 = Test("Positif"); } static public bool Test(string test) { return test.Length < 15; }

Une fonction est un bloc de traitements qui retourne un rsultat, et la mthode ne fait que des traitements sans rien retourner. Pour allger le texte, d'ici la fin de ce tutoriel, j'appellerai toute fonction ou mthode des mthodes, indiffremment de leur traitement.

On voit clairement une situation trs usuelle ici. V ous appelez Test deux fois partir du Main. Ce qui se passera, c'est que lorsque viendra le temps d'excuter ce code, .NET lancera la mthode Test afin de donner un rsultat la variable resultat . On fait alors un appel de la mthode. Je vais immdiatement dclarer un delegate pour la mme situation, comme a, vous verrez de quoi il en retourne. Code : C# using using using using System ; System.Collections.Generic; System.Linq; System.Text;

namespace TestThread { class Program { //Mon delegate aura exactement la mme signature que ma mthode ! delegate bool PremierDelegate(string i); static void Main(string[] args) { //Je cre une variable a qui contiendra la mthode PremierDelegate a = new PremierDelegate(Test); //Au lieu d'appeler Test, je vais appeler a, ce qui me //mme rsultat !

Test.

donnera le

www.openclassrooms.com

Les Threads en .NET


bool resultat = a("Ceci est un test qui est ngatif !"); bool res2 = a("Positif");

5/31

static public bool Test(string test) { return test.Length < 15; }

Bon, dans cet exemple, l'utilisation d'un delegate est carrment inutile, mais ils deviendront rapidement indispensables, surtout lors de la programmation rseau. Une autre utilisation trs frquente des delegates est la gestion des vnements ! Quand vous vous abonnez un vnement , vous refilez tout simplement une mthode une liste de delegate. Quand on invoque un vnement, on appelle toutes les mthodes abonnes. Les vnements peuvent faire l'objet d'un autre tutoriel et dpassent les objectifs de ce tutoriel. Si vous voulez plus d'informations, veuillez visiter le site de MSDN en attendant. Dans cet exemple, je me rfre au concept de signature d'une mthode. Je vais prendre quelques minutes pour vous expliquer.

Les signatures de mthodes


Toute mthode possde une signature. Une signature de mthode, comme dans le monde rel, sert identifier une mthode de faon unique. Si vous avez dj fait de la programmation, peut-tre avez-vous dj vu ce concept. La signature de mthode rsout le problme des noms uniques dans le cas de surcharge d'une mthode. Dans les langages autorisant la surcharge de mthode, on se rfre la signature plutt qu'au nom de la mthode pour l'appeler. Pour chaque mthode doit correspondre une signature diffrente des autres. Je vais vous faire un cours en acclr, donc je vous invite vous rfrer la documentation de MSDN ou alors au Forum du Site du Zro si vous avez de plus amples questions. Donc, la signature d'une mthode contient les informations suivantes : L'identificateur de la mthode La squence des types de la liste des paramtres

Le type de retour ne fait pas partie de la signature ! En effet, ce qui est important de retenir est le Nom de la mthode et les paramtres dans l'ordre.

Ce qui fait que notre mthode static public int Test(string test) a la signature suivante : Test(string); La dfinition d'un delegate est un peu diffrente. Le nom de la mthode et des paramtres sont inutiles. Ce qui compte c'est le type de retour et l'ordre des paramtres selon leur type (pas leur nom). Ainsi, un delegate ne pourra rfrer qu'une mthode possdant la mme dfinition .

L'autopsie d'un delegate !


Analysons maintenant comment crer ce fameux delegate. Tout d'abord, sachez que le rle principal d'un delegate est de passer, croyez-le ou non, une mthode en paramtre une autre mthode. On peut alors aisment imaginer la flexibilit d'un code en permettant une mthode gnrique d'appeler une mthode passe en paramtre et d'en afficher le rsultat, peu importe les oprations effectuer. Nous verrons un exemple un peu plus tard. Pour le moment, voici la dfinition d'un delegate : [Attribut] [Modificateur d'accs] delegate typeRetour NomDuDelegate (paramtres)

Les cases entours de crochets sont optionnels. Un attribut, par exemple, sert injecter des meta-donnes rcuprables l'excution du programme. Cela dpasse le cadre de ce cours, mais vous pouvez vous rabattre sur

www.openclassrooms.com

Les Threads en .NET

6/31

MSDN pour plus d'explications. Les modificateurs d'accs , quant eux, sont une notion qui devrait vous tre connue. Il s'agit de modifier l'accs l'aide des mots cls public, private, protected, internal , etc.

V oici un exemple :

Je parlais un peu plus haut de la dfinition d'un delegate. Il faut bien la lui donner cette dfinition ! On fait gnralement cela dans le mme espace o l'on dclare les variables globales. V ous verrez dans l'exemple qui suit. Ensuite, on utilise le delegate comme un objet. On peut alors l'utiliser dans les paramtres ou ailleurs si ncssaire. Ce sera plus clair pour vous avec l'exemple qui suit : Code : C# using using using using System ; System.Collections.Generic; System.Linq; System.Text;

namespace TestThread { class Program { delegate int Calcul(int i1, int i2); static void Main(string[] args) { //Affichage de la console. Console.WriteLine("Test de delegate"); Console.WriteLine("----------------------"); //On passe la mthode Afficher la mthode lancer et les arguments. Afficher(Add, 25, 19); Afficher(Sub, 52, 17); Afficher(Mul, 10, 12); Afficher(Div, 325, 5); //On ne ferme pas la console immdiatement. Console.ReadKey();

www.openclassrooms.com

Les Threads en .NET


//On fait une mthode gnrale qui prendra le delegate en paramtre. static void Afficher(Calcul calcul, int i, int j) { Console.WriteLine("{0} {1} {2} = {3}", i, calcul.Method.Name, j, calcul(i, j)); } //Mthodes trs simples qui ont toutes un des paramtres identiques. static int Add(int i, int j) { return i + static int Sub(int i, int j) { return i static int Mul(int i, int j) { return i * static int Div(int i, int j) { return i / } } type de retour et j; j; j; j; } } } }

7/31

C'est pas du chinois, mais c'est pas simple, hein ? En effet, a arrache une grimace la premire fois qu'on voit a, mais en fait, c'est trs simple. Comme toutes nos mthodes rpondent la mme dfinition que notre delegate, nous sommes en mesure de toutes les utiliser l'aide du mme. C'est un peu comme de dire qu'un int peut galer 0 ou bien 12154, car les deux rpondent la mme dfinition, soit tre un entier entre int.Min et int.Max . Ce code, bien que simple est trs puissant. Si je voulais ajouter l'opration modulo, il serait TRS TRS simple de le faire, vous ne trouvez pas ? Cela conclut notre introduction aux delegates. Il y en a plus que a savoir et comprendre, mais si vous matrisez cette partie, c'est excellent. Si vous ne matrisez pas bien, je vous recommande de la relire ! Si vous avez fait du C ou du C++, cette fonctionnalit ressemble trangement aux pointeurs sur fonction. Lorsque l'on fera de la programmation rseau, ces delegates devriendront essentiels ! Courage, c'tait vraiment le plus difficile ...

Les threads Un peu d'histoire


On parle beaucoup de threads ces temps-ci. Les nouveaux processeurs sont des puces conues afin d'optimiser le traitement de plusieurs threads simultans. Il y a quelques annes, l're du Pentium 4, on ne cessait d'augmenter la frquence d'horloge afin d'optimiser la vitesse d'un seul coeur. Chaque thread avait un numro et attendait son tour afin d'tre trait. Ne vous mprenez pas, cela n'a pas chang, mais les processeurs ont commenc les traiter simultanment, d'abord avec la technologie HT sur les derniers Pentium 4, puis l'aide de multiples coeurs avec les Core 2 Duo / Quad. Maintenant, il s'agit d'un mlange des deux, soit de multiples coeurs qui appliquent chacun une technologie HT, comme dans le Core i7 d'Intel. Pardonnezmoi, mais je connais trs mal les processeurs AMD, tant un utilisateur d'Intel majoritairement . Cela explique un peu l'volution de la technique de traitement des threads, mais je ne vous ai toujours pas expliqu comment fonctionne le multi-task en Windows.

Le multi-task en Windows
Un ordinateur, a ne sait faire qu'une seule chose la fois ! Windows, comme tout bon SE actuel se sert d'une mthode particulire afin de simuler un multi-task. En effet, un processeur ne sait que faire une chose la fois. La technique est bien simple, il s'agit de crer un systme de jeton et de le passer chaque processus pour un certain temps selon leur priorit. En ce moment mme, vous utilisez votre navigateur prfr pour visiter le site du Zro, mais cela n'empche pas votre ordinateur de vaquer d'autres occupations. Par exemple, vous tes peut-tre en train de copier un fichier, ou mme juste en train d'avoir 3 fentres ouvertes sur le Bureau en ce moment. Windows doit rafrachir leur contenu toutes les x millisecondes afin de crer un sentiment de fluidit chez l'utilisateur. Donc, suivant cet exemple, Windows aura un jeton accorder votre navigateur web pour tant de temps, puis suspendra ses calculs et oprations et donnera le jeton un autre traitement. Lorsqu'il reviendra au navigateur, celui-ci sera autoris continuer

www.openclassrooms.com

Les Threads en .NET

8/31

ses oprations. Comme a, tout le monde est content, mais surtout l'utilisateur qui dsire ouvrir Google Chrome en mme temps que MSN et Word, ainsi que Visual Studio 2010. Ne riez pas, c'est pas mal le scnario actuel de mon PC en ce moment... Tout a pour dire qu'on commence la section sur les threads pour de vrai !

Les threads, enfin !


Les threads sont des excutions que l'on spare de l'excution principale pour les raisons suivantes : Tche exigeante (gros calculs, gros traitements, etc)... Tche dtache (impression, recherche, etc)... Tche bloquante (a sent le rseau ici !)... Crer un thread dans ces cas est utile afin de crer un certain paralllisme dans les excutions. Les threads rendent un code beaucoup plus complexe, non seulement l'criture, mais aussi au dbogage ! Oh l l, que de frustration passes programmer avec les threads. Cela dit, ils apportent normment de puissance une application ! Cette complexit vient du fait que le systme de jeton est imprvisible ! Par exemple, il pourrait passer 3 secondes sur un thread A, mais 2 secondes sur un thread B. Bien-sr, on ne parle pas de secondes ici, mais bien de nano-secondes. Un autre point frustrant sera que lors du dboggage, vous pourriez vous retrouver dans une mthode compltement diffrente de celle qui vous intresse en un clin d'oeil, justement cause que le systme de jeton a chang de thread et vous a projet dans la mthode qu'il excute au moment mme.

Le cas des tches bloquantes...


Une application Windows Forms est amene se rafrachir assez frquemment. Lors de grosses oprations ou d'oprations synchrones qui bloquent, tout le temps de calcul est allou ces tches et non plus au rafrachissement. Aprs un certain temps, Windows dclare l'application comme "Ne rpondant plus". Si vous planifiez de distribuer votre application, ce comportement est inacceptable, vous en conviendrez. C'est dans ce type de cas qu'on utilisera les threads. Imaginez afficher une belle animation sur un Splash Screen alors que les ressources sont en chargement en arrire plan.

Les diffrents types de thread


Il est possible de crer deux types de threads, bien que cela revienne au mme. Lors de l'instanciation de la classe Thread, il est possible de garder la rfrence de l'objet, ou de la laisse flotter. Si on ne la rcupre pas, on appellera ce thread "indpendant". Il sera cr, puis lanc immdiatement. Comme on ne gardera pas la rfrence, on ne pourra pas contrler ce thread du tout. Il fera ce qu'il a faire, sans que l'on puisse intervenir (sauf en utilisant quelques primitives de synchronisation que nous verrons plus tard). V oici comment dclarer un thread indpendant : Code : C# new Thread(fonction).Start();

Puisqu'il n'y a pas d'oprateur d'affectation ( = ), on laisse partir la rfrence. Lorsque le thread se terminera, le Garbage Collector passera en arrire et se dbarrassera de l'objet pour nous.

Le type dpendant est beaucoup plus frquent. Il s'agit de garder la rfrence sur l'objet afin de pouvoir l'analyser, le tester, l'influencer. V ous connaissez dj tous comment le crer, mais pour la forme, voici un exemple : Code : C# Thread nomThread = new Thread(fonction);

Cela peut porter confusion, mais tous les threads d'une mme application sont appels Processus dans l'environnement Windows.Ceci provient du fait que Windows dsire grouper tous les threads appartenant une

www.openclassrooms.com

Les Threads en .NET

9/31

application au cas o a tournerait mal. Cependant, le systme de jeton mentionn ci-haut fonctionne au niveau des threads lui-mme, et pas des processus. Cela signifie galement que de mettre fin au thread principal met en effet fin aux threads enfants, indpendants ou dpendants, car on dtruit carrment le processus et les threads qui le composent.

Comment lancer le thread ?


Tout d'abord, assurez-vous d'utilisez l'espace de nom (vous savez, les using tout en haut de votre fichier .cs) using System.Threading;. Dclarer un nouveau thread va comme suit : Code : C# using using using using using System ; System.Collections.Generic; System.Linq; System.Text; System.Threading;

namespace TestThread { class Program { static void Main(string[] args) { //On initialise l'object Thread en lui passant la mthode // excuter dans le nouveau thread. a vous rappelle pas //certains delegates a ? Thread th = new Thread(Afficher); indiquer de //Un thread, a ne part pas tout seul. Il faut lui //commencer l'excution. th.Start(); Console.ReadKey();

static void Afficher() { //Code tout bte qui affiche la lettre A 1000 fois. for (int i = 0; i < 1000; i++) { Console.Write("A"); } }

Ce code fonctionne bien dans le cas o on n'a aucun paramtre passer. Il est un peu plus compliqu d'en passer, mais on s'en sort, vous verrez. Code : C# using using using using using System ; System.Collections.Generic; System.Linq; System.Text; System.Threading;

namespace TestThread { class Program {

www.openclassrooms.com

Les Threads en .NET


static void Main(string[] args) { //Il faut crer un objet ParameterizedThreadStart dans le constructeur //du thread afin de passer un paramtre. Thread th = new Thread(new ParameterizedThreadStart(Afficher)); Thread th2 = new Thread(new ParameterizedThreadStart(Afficher)); //Lorsqu'on excute le thread, on lui donne son paramtre de type Object. th.Start("A"); th2.Start("B"); } Console.ReadKey(); {

10/31

//La mthode prend en paramtre un et un seul paramtre de type Object. static void Afficher(object texte) { for (int i = 0; i < 10000; i++) { //On crit le texte passer en paramtre. N'oubliez pas de le caster //car il s'agit d'un type Object, pas String. Console.Write((string)texte); } Console.WriteLine("<------------Thread {0} termin---------->", (string)texte); } } }

Attention, la plus frquente source d'erreur lors de l'utilisation de ce genre de thread est le paramtre. En effet, ce type de delegate a sa propre dfinition et il requiert un seul et unique paramtre qui sera de type object . Fates bien attention transtyper (cast) vos variables correctement !

Cet exemple est parfait pour vous montrer comment les threads sont imprvisibles, et c'est ce qui les rend compliqus ! Je vous montre le rsultat chez moi.

www.openclassrooms.com

Les Threads en .NET

11/31

C'est pas bien beau tout a. Je suis sr que chez vous, c'est tout--fait diffrent. Mme si je ne fais que le redmarrer, ce sera diffrent ! La leon retenir ici est que les threads sont imprvisibles, comme je l'ai expliqu plus haut. Pour preuve, j'ai relanc le mme processus un peu plus tard, et voici le rsultat :

www.openclassrooms.com

Les Threads en .NET

12/31

On voit trs bien que dans ce cas-ci, le Thread B a termin en premier, ce qui prouve que le mme code peut gnrer des rsultats diffrents d'une excution l'autre, s'il est cod avec des threads !

Cas particuliers
Mme si un thread s'excute en de de votre programme principal, il reste que la mthode qu'il excute fait partie de la classe laquelle la mthode appartient. Cela signifie que l'accs aux variables globales et membres de votre classe lui seront accessibles sans problme. L o le problme se pose, c'est lorsque plusieurs threads devront accder la mme variable, y faire des changements et des tests. Imaginez que votre thread A accde aux variables nominateuret dnominateur qui sont globales ( proscrire, mais bon). Le thread A a le temps de faire quelques tests, savoir vrifier si le dnominateur n'est pas gal zro avant de procder une division. Tous les tests passent, mais juste au moment o le thread arrive pour effectuer l'opration, le thread B s'empare du jeton. Le thread B est charg de rinitialiser le dnominateur 0, et c'est ce qu'il fait. ce moment l, le jeton revient au thread A qui tente d'effectuer la division. Oops, a plante... C'est ce qu'on appelle un problme de synchronisation. Je ne vais pas vous mentir, ces problmes sont rares. Il faut vraiment que vous soyez malchanceux. Il reste cependant important de bien synchroniser ses threads, surtout si l'on aspire commercialiser le produit. Ainsi, plusieurs structures de synchronisation existent, et nous allons en survoler quelques unes.

Les mcanismes de synchronisation


Les variables de contrle
Il peut sembler que les variables de contrle soient un concept trs pouss, mais pas du tout ! Il s'agit btement d'une variable globale que seul le thread principal modifiera et que les threads enfants contrleront. Ce concept est particulirement efficace dans le cas o le thread effectue une boucle infinie. Encore un fois, a sent la programmation rseau ici. Je vous illustre le concept l'aide d'un bte exemple. Code : C# using using using using using System ; System.Collections.Generic; System.Linq; System.Text; System.Threading;

namespace VarControle { class Program

www.openclassrooms.com

Les Threads en .NET


{ //Quelques variables porte globale. private static bool _quitter = false; private static int _identificateur = 0; static void Main(string[] args) { Console.Title = "Variables de contrle"; //On cre un tableau de threads. Thread[] threads = new Thread[5]; les threads. //On itre travers le tableau afin de crer et lancer for(int i = 0; i< threads.Length; i++) { //Cration et lancement des threads. threads[i] = new Thread(OperThread); threads[i].Start(); //On laisse passer 500ms entre les cration de } Thread.Sleep(500);

13/31

thread.

//On demande ce que tous les threads quittent. _quitter = true; } Console.ReadKey();

static void OperThread() { //On donne au thread un identificateur unique. int id = ++_identificateur; Console.WriteLine("Dbut du thread {0}", id); while (!_quitter) { //On fait des choses ici tant qu'on ne dsire pas Console.WriteLine("Thread {0} a le contrle", id); //On met le thread en tat de sommeil pour 1000ms / } } Thread.Sleep(1000);

quitter...

1s.

Console.WriteLine("Thread {0} termin", id);

V oici le rsultat :

www.openclassrooms.com

Les Threads en .NET

14/31

Ce qu'il faut comprendre de cet exemple, c'est que les variables de contrle sont une bonne mthode afin d'influencer le comportement d'un thread, mais gnralement seulement lorsque celui-ci est en boucle. Aussi, il est TRS important de retenir que seul le thread principal doit modifier la valeur de cette variable. Sinon, on pourrait retrouver des threads toutes les sauces. Imaginez qu' chaque itration, la boucle change la valeur de la variable de contrle. On ne sait pas quand ou comment cela se produira et les problmes feraient probablement vite leur apparition. Somme toute, il s'agit d'une bonne mthode ne pas utiliser outrance.

Avez-vous remarqu un bout de code qui ne vous semblait pas thread-safe ? Si oui, vous comprendrez certainement l'utilit du prochain mcanisme de synchronisation.

Secret (cliquez pour afficher) Code : C# //On donne au thread un identificateur unique. int id = ++_identificateur;

Ce bout de code n'est pas thread-safe, car on ne sait pas si un autre processus pourrait prendre le contrle au mauvais moment. Si l'ordre de lancement est trs important, cette ligne pourrait ne pas s'excuter temps.

Le lock
L'instruction lock permet de verrouiller efficacement une ressource tant et aussi longtemps qu'un bloc d'instruction est en cours. Cela signifie que si d'autres threads tentent d'accder la mme ressource en mme temps, ils ne pourront pas. Cela ne signifie pas qu'ils planteront et se termineront, mais plutt qu'ils passeront le jeton un autre thread et attendront patiemment leur tour afin d'accder cette ressource. V oici un bel exemple : Code : C# using System ; using System.Collections.Generic; using System.Linq;

www.openclassrooms.com

Les Threads en .NET


using System.Text; using System.Threading; namespace Lock { class Program { //Variable tmoin du lock. private static Object _lock = new Object(); //Sert initialiser des valeurs pseudos-alatoires. private static Random _rand = new Random((int)DateTime.Now.Ticks); //Variable de contrle. private static bool _quitter = false; //Variables globales tant affectes par les threads. private static int _nominateur; private static int _denominateur; static void Main(string[] args) { Console.Title = "Dmonstration des lock"; //On cre les threads. Thread init = new Thread(Initialiser); init.Start(); Thread reinit = new Thread(Reinitialiser); reinit.Start(); Thread div = new Thread(Diviser); div.Start(); //On les laisse travailler pendant 3 seconde. Thread.Sleep(3000); //Puis on leur demande de quitter. _quitter = true; } Console.ReadKey();

15/31

private static void Initialiser() { //Boucle infinie contrle. while (!_quitter) { //On verouille l'accs aux variables tant que l'on a pas termin. lock (_lock) { //Initialisation des valeurs. _nominateur = _rand.Next(20); _denominateur = _rand.Next(2, 30); } //On recommence dans 250ms. Thread.Sleep(250);

private static void Reinitialiser() { //Boucle infinie contrle. while (!_quitter) { //On verouille l'accs aux variables tant que l'on a pas termin. lock (_lock)

www.openclassrooms.com

Les Threads en .NET


{ //Rinitialisation des valeurs. _nominateur = 0; _denominateur = 0;

16/31

//On recommence dans 300ms. Thread.Sleep(300);

private static void Diviser() { //Boucle infinie contrle. while (!_quitter) { //On verouille pendant les oprations. lock (_lock) { //Erreur si le dnominateur est nul. if (_denominateur == 0) Console.WriteLine("Division par 0"); else { Console.WriteLine("{0} / {1} = {2}", _nominateur, _denominateur, _nominateur / (double)_denominateur); } } //On recommence dans 275ms. Thread.Sleep(275);

Rsultat :

C'est bien comique parce que lorsque je vous ai prpar cet exemple, l'erreur dont je vous ai mentionn plus tt s'est produite. Je n'avais alors pas englob mon test du dnominateur dans l'instruction lock, ce qui a permis au thread en charge de rinitialiser les valeurs d'embarquer. Cela a produit une erreur de type NaN (Non-Numrique).

www.openclassrooms.com

Les Threads en .NET

17/31

Malheureusement, sur le coup, je n'ai pas fait de capture d'cran, et j'ai donc essay de reproduire l'erreur, mais sans succs. C'est donc vous dire qu'avec les threads, il faut tout prvoir ds le dpart, car l'apparition d'une erreur peut tre un heureux (ou malheureux) hasard !

Donc, dans cet exemple, on voit que tout est bien protg. Aucun thread ne peut venir interfrer avec les autres. Remarquez la cration d'une instance d'un objet de type Object la ligne 12. Cela est notre tmoin de verrouillage. En ralit, n'importe quel objet qui se passe en rfrence peut servir de tmoin de verrouillage. Comme nous avons travaill avec des int dans cet exemple et que ce type est pass par valeur, nous avons eu crer cette variable. V oici un exemple o une variable tmoin est inutile : Code : C# using using using using using System ; System.Collections.Generic; System.Linq; System.Text; System.Threading;

namespace lockEx { class Program { static List<string> liste = new List<string>(); static void Main(string[] args) { for (int i = 0; i < 6; i++) new Thread(Ajouter).Start(); } static void Ajouter() { lock(liste) liste.Add("abc"); }

Ici, on utilisera donc l'objet liste qui se passe par rfrence, et qui est donc acceptable. Il est possible de crer un lock en lui spcifiant un nom de type string. Cependant, Microsoft ne le recommande pas, car cette notation pourrait amener de la confusion. N'oubliez pas non plus qu'il est possible de faire de multiples lock qui ne protgent pas les mmes ressources (indpendants les uns des autres). Il suffit de changer la variable tmoin pour accommoder la situation.

Les Mutex
Les Mutex sont excessivement similaires aux lock. Cependant, si vous dsirez crer de nombreuses sections critiques indpendantes, les Mutex ont l'avantage d'tre sous forme d'objets plutt que d'instructions. Un petit exemple vous claira sur l'utilisation des Mutex. Code : C# using using using using using System ; System.Collections.Generic; System.Linq; System.Text; System.Threading;

namespace MutexEx {

www.openclassrooms.com

Les Threads en .NET


class Program { private const int TAILLE_TABLEAU = 2; //On cre les Mutex. private static Mutex _muxMultiplier = new Mutex(); private static Mutex _muxDiviser = new Mutex(); //On cre les tableaux de valeurs. private static int[] _valDiv = new int[TAILLE_TABLEAU]; private static int[] _valMul = new int[TAILLE_TABLEAU]; //Objet Random et variable de contrle. private static Random _rand = new Random((int)DateTime.Now.Ticks); private static bool _quitter = false; static void Main(string[] args) { Console.Title = "Exemple de Mutex"; //On cre et on dmarre les threads. Thread init = new Thread(Initialiser); init.Start(); Thread mul = new Thread(Multiplier); mul.Start(); Thread div = new Thread(Diviser); div.Start(); //On laisse les threads fonctionner un peu... Thread.Sleep(3000); //On demande ce que les oprations se terminent. _quitter = true; } Console.ReadKey();

18/31

private static void Initialiser() { while (!_quitter) { //On demande au thread d'attendre jusqu' ce qu'il ait le contrle sur les Mutex. _muxMultiplier.WaitOne(); _muxDiviser.WaitOne(); for (int i = 0; i < TAILLE_TABLEAU; i++) { //On assigne au tableau de nouvelles valeurs. _valMul[i] = _rand.Next(2, 20); _valDiv[i] = _rand.Next(2, 20); } Console.WriteLine("Nouvelles valeurs !"); //On relche les Mutex _muxDiviser.ReleaseMutex(); _muxMultiplier.ReleaseMutex(); //On tombe endormi pour 100ms. Thread.Sleep(100);

private static void Multiplier() { while (!_quitter) {

www.openclassrooms.com

Les Threads en .NET


//On demande le Mutex de multiplication. _muxMultiplier.WaitOne(); //On multiplie. Console.WriteLine("{0} x {1} = {2}", _valMul[0], _valMul[1], _valMul[0] * _valMul[1]); //On relche le Mutex. _muxMultiplier.ReleaseMutex(); //On tombe endormi pour 200ms. Thread.Sleep(200);

19/31

private static void Diviser() { while (!_quitter) { //On demande le Mutex de division. _muxDiviser.WaitOne(); //On divise. Console.WriteLine("{0} / {1} = {2}", _valDiv[0], _valDiv[1], _valDiv[0] * _valDiv[1]); //On relche le Mutex de Division. _muxDiviser.ReleaseMutex(); //On tombe endormi pour 200ms. Thread.Sleep(200);

www.openclassrooms.com

Les Threads en .NET

20/31

Si vous avez fait un peu de programmation en Win32 (langage C), vous pouvez voir la ligne directe des Mutex du .NET et des CRITICAL_SECTION du Win32. Sinon, vous voyez que les Mutex ont la mme fonction que l'instruction lock en un peu plus verbeux. Je tiens cependant vous avertir que de ne pas relcher un Mutex peut faire planter votre application, donc fates attention cela.

SemaphoreSlim
Le SemaphoreSlim sert contrler l'accs d'une ressource limite. Jusqu' maintenant, les mcanismes de synchronisation dont nous avons parl ont surtout servi limiter une ressource un accs mutuellement exclusif entre des threads concurrents. Quant est-il si l'on veut partager une ressource, mais travers plusieurs threads simultanment ? Cependant, on aimerait garder un

www.openclassrooms.com

Les Threads en .NET

21/31

nombre maximal d'accs concurrent la ressource. Les smaphores existent pour cette raison. En C# .NET, il existe deux types de smaphores. Le classique Semaphore et le SemaphoreSlim. La diffrence provient de la complexit de l'objet et des mcanismes internes. Le Semaphore utilise un wrapper autour de l'objet Semaphore du Win32 et rend donc disponible ses fonctionnalits en .NET. Le SemaphoreSlim, lui, est plutt utilis lors de courtes dures d'attente et utilise les mcanismes propres au CLR. Je ne montrerai que le SemaphoreSlim, les deux se ressemblant beaucoup. Cependant, le SemaphoreSlim reste le plus facile et le plus lger implmenter. Pour plus d'information sur la diffrence, veuillez lire cet article sur MSDN. Peu importe la version qui est choisi, vous pouvez voir les Smaphores comme un "doorman" dans une bote de nuit. La place l'intrieur est limite et le doorman devra contrler l'accs la ressource. Code : C# using using using using using System ; System.Collections.Generic; System.Linq; System.Text; System.Threading;

namespace SemaphoreSlimEx { class Program { //Dclaration du SemaphoreSlim qui prendra en paramtre le nombre de places disponibles. static SemaphoreSlim doorman = new SemaphoreSlim(3); static void Main(string[] args) { Console.Title = "Exemple de SemaphoreSlim"; //Cration des threads. for (int i = 0; i < 10; i++) new Thread(Entrer).Start(i); Console.ReadKey();

static void Entrer(object n) { Console.WriteLine("La personne #{0} veut entrer", n); //Le doorman attendra qu'il y ait de la place. doorman.Wait(); Console.WriteLine("#{0} vient d'entrer dans le bar", n); Thread.Sleep((int)n * 1000); Console.WriteLine("#{0} a quitt le building !", n); d'autre. } } //Le doorman peut maintenant faire entrer quelqu'un doorman.Release();

www.openclassrooms.com

Les Threads en .NET

22/31

Le Join()
C'est le dernier mcanisme de synchronisation dont je parlerai. Il s'agit trs simplement d'attendre la fin d'un autre thread afin de continuer le thread dans lequel le Join() est dfini. Cela en fait une mthode bloquante qui pourrait vous causer des problmes en Windows Forms. Petit exemple : Code : C# using using using using using System ; System.Collections.Generic; System.Linq; System.Text; System.Threading;

namespace TestThread { class Program { static void Main(string[] args) { Thread th = new Thread(new ParameterizedThreadStart(Afficher)); Thread th2 = new Thread(new ParameterizedThreadStart(Afficher)); th.Start("A"); thread B. //On attend la fin du thread A avant de commencer le th.Join();

www.openclassrooms.com

Les Threads en .NET


th2.Start("B"); } Console.ReadKey();

23/31

static void Afficher(object texte) { for (int i = 0; i < 10000; i++) { Console.Write((string) texte); } }

Le Abort()
Bon, aprs avoir vu comment bien synchroniser ses threads, voyons ce que vous ne devez Code : C# using using using using using System ; System.Collections.Generic; System.Linq; System.Text; System.Threading;

PAS faire !!!

namespace ThreadStop { class Program { static void Main(string[] args) { Thread thread = new Thread(Test); thread.Start(); Thread.Sleep(100); //On tue le processus. NE PAS FAIRE ! thread.Abort(); } Console.ReadKey();

public static void Test() { for(int i = 0; i < 10000; i++) Console.WriteLine(i); }

Aux premiers abords, cela semble assez facile faire, et semble sans grandes consquences. En fait, vous avez probablement raison dans cet exemple. Cependant, ne prenez pas l'habitude de faire terminer vos threads si abruptement, car il se pourrait que cela vous cause des erreurs ventuellement. En effet, vous ne fermez pas votre ordinateur en dbranchant la prise du mur, n'est-ce pas ? ( N'est-ce pas ?). C'est le mme principe ici. Si vous tiez en train de faire quelque chose de vraiment important, et que le thread principal choisirait ce moment pour arrter le thread, l'application en entier pourrait devenir instable et planter. Puisqu'on ne veut pas cela, il vaut mieux utiliser le Join() et une variable de contrle, comme dans l'exemple suivant :

www.openclassrooms.com

Les Threads en .NET


Code : C# using using using using using System ; System.Collections.Generic; System.Linq; System.Text; System.Threading;

24/31

namespace ThreadStop { class Program { private static bool _continuer = true; static void Main(string[] args) { Thread thread = new Thread(Test); thread.Start(); Thread.Sleep(100); //On demande au thread de s'arrter au prochain passage d'un moment qui semble naturel. _continuer = false; //On attend que le thread se termine. thread.Join(); } Console.ReadKey();

public static void Test() { //On fait 10 000 itrations, tant et aussi longtemps que l'on peut continuer (variable de contrle). for(int i = 0; i < 10000 && _continuer; i++) Console.WriteLine(i); } } }

Et voil, on se sent toujours mieux quand on fait quelque chose de bien, non ? Comme a, si le thread a besoin de temps pour bien terminer ses oprations (appeler quelques Dispose() , ou fermer des connexions TCP), il le pourra. Utilisez donc les Join() et pas les Abort() . Les Abort() , c'est mal ... Lorsque vous fermez l'application, Windows tend simplement appeler Abort() sur les threads en fonction. Ce comportement est hrit du Win32 dont le .NET recouvre l'aide de wrappers (il s'agit d'une longue histoire, croyezmoi). C'est aussi pourquoi on vitera autant que possible l'usage de threads indpendants, car ils sont moins contrlables. Prvoyez donc attendre les threads actifs lorsque vous quitter votre application, car comme nous l'avons dit, les Abort() , c'est mal. Quoique avec le Garbage Collector de .NET.... NON, NON, C'EST MAL !

Nous sommes maintenant prts aborder le sujet du multi-tche en Windows Forms ! Je vous montrerai comment viter que cela ne vire en catastrophe, ne craignez rien.

Les threads avec les Windows Forms


Lorsque viendra le temps de faire du rseau, il sera important de respecter la scurit interne des ressources. Il sera trs souvent intressant de modifier un objet Windows Forms avec les donnes issues d'un autre thread. C'est notamment le cas en rseau, si on fait un programme de ch@t , par exemple. Rappelez-vous, un peu plus haut, quand je vous ai dit qu'un thread, mme s'il est spar, a accs aux membres de sa classe ? Je

www.openclassrooms.com

Les Threads en .NET

25/31

ne vous ai pas menti. Mais dans le cas des objets Windows Forms, ceux-ci ne sont pas bties thread-safe et donc que .NET limite leurs possibilits en multi-thread. Cela signifie que, malheureusement, vous ne pourrez accder leurs proprits qu'en lecture seule si vous ne fates pas parti du mme thread. Je vous donne un exemple : J'ai construit une Windows Forms trs simple n'ayant que deux lments : Une ProgressBar Un Bouton

J'ai ajout le code suivant dans le fichier Form1.cs. Code : C# using using using using using using using using using System ; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms; System.Threading;

namespace ThreadWForms { public partial class Form1 : Form { private Random rand = new Random((int)DateTime.Now.Ticks); private int[] tableau = new int[100000000]; public Form1() { InitializeComponent(); //On gnre un tableau d'entiers alatoires. for (int i = 0; i < tableau.Length; i++) { tableau[i] = rand.Next(50000); //...Boucle trs simple, avec mthode Random trs simple. } } public void Selection() { //On va simplement compter les nombres du tableau infrieurs 500. int total = 0; for (int i = 0; i < tableau.Length; i++) { if (tableau[i] < 500) { total++; } //Puis, on incrmente le ProgressBar. pgbThread.Value = (int)(i / (double)tableau.Length *

100);

www.openclassrooms.com

Les Threads en .NET


} }

26/31

private void btnLancer_Click(object sender, EventArgs e) { //On cre le thread. Thread t1 = new Thread(new ThreadStart(Selection)); //Puis on le lance ! t1.Start();

Ce code n'est pas bon, bien que logique. Cela produira cette erreur :

Alors, je vous l'avais pas dit que a ne marcherait pas ? Il existe heureusement une faon bien simple de contrer ce problme, et c'est de passer par les delegates ! En effet, car si ceux-ci peuvent tre passs en paramtres, il peuvent aussi servir excuter des oprations sur un thread diffrent ! Code : C# using using using using using using using using using System ; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms; System.Threading;

namespace ThreadWForms { public partial class Form1 : Form { private Random rand = new Random((int)DateTime.Now.Ticks); private int[] tableau = new int[500000]; //On cre notre delagate. public delegate void MontrerProgres(int valeur); bool termine = true;

www.openclassrooms.com

Les Threads en .NET


public Form1() { InitializeComponent(); //On gnre un tableau d'entiers alatoires. for (int i = 0; i < tableau.Length; i++) { tableau[i] = rand.Next(50000); //...Boucle trs simple, avec mthode Random trs simple. } } public void Selection() { //On va simplement compter les nombres du tableau infrieurs 500. int total = 0; for (int i = 0; i < tableau.Length; i++) { if (tableau[i] < 500) { total++; } //Puis, on incrmente le ProgressBar. int valeur = (int)(i / (double)tableau.Length * //On achte la paix, on entoure notre Invoke d'un try {

27/31

100); try...catch !

tche sur le temps

//On invoque le delegate pour qu'il effectue la //de l'autre thread. Invoke((MontrerProgres)Progres, valeur);

} }

} catch (Exception ex) { return; }

termine = true;

temps.

private void btnLancer_Click(object sender, EventArgs e) { //Petite scurit pour viter plusieurs threads en mme if (termine) { //On cre le thread. Thread t1 = new Thread(new ThreadStart(Selection)); termine = false; //Puis on le lance ! t1.Start();

public void Progres(int valeur) { //On met la valeur dans le contrle Windows Forms. pgbThread.Value = valeur; }

www.openclassrooms.com

Les Threads en .NET

28/31

Petites explications : Un Invoke sert demander l'autre thread de s'occuper d'une action dans un moment libre. C'est l'quivalent d'envoyer un email un webmestre pour qu'il corrige une erreur sur sa page web. Le webmestre, ds qu'il le pourra, s'occupera de corriger l'erreur. Il s'agit du mme principe ! Cela permet de contourner le manque thread-safe des contrles Windows Forms, car c'est le thread propritaire qui finit par effectuer l'action. Aussi, vous avez peut-tre fait la grimace en apercevant la ligne 55. Il y a un cast d'un delegate juste avant le nom de la mthode. Cela vite d'avoir crer un delegate. Je pourrais trs bien remplacer cette ligne par ceci : Invoke(new MontrerProgres(Progres), valeur);.

BackgroundWorker
La classe BackgroundWorker fournit un environnement d'excution multitche trs scuritaire, mais un peu limit mon got. Cependant, je vais quand mme vous montrer comment l'utiliser. L'objet BackgroundWorker se trouve dans la barre d'outils dans la catgorie Composants. Il s'agit d'un petit objet pas trs personnalisable qui possde trs peu de paramtres et dvnements. Je vous montre par un exemple comment l'utiliser. J'ai fait un simple projet Windows Forms dans Visual Studio qui comporte une ProgressBar et un bouton de dpart, tout comme l'exemple prcdent.

Code : C# using using using using using using using using using System ; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms; System.Threading;

namespace Background_WorkerEx { public partial class Form1 : Form { private bool _etat = false; public Form1() { InitializeComponent(); //On demande ce que le BackgroundWorker supporte le rapport de progrs et l'annulation. bwProgress.WorkerReportsProgress = true; bwProgress.WorkerSupportsCancellation = true; //On abonne le BackgroundWorker aux venements requis. bwProgress.DoWork+=new DoWorkEventHandler(bwProgress_DoWork); bwProgress.ProgressChanged+=new ProgressChangedEventHandler(bwProgress_ProgressChanged); bwProgress.RunWorkerCompleted+=new RunWorkerCompletedEventHandler(bwProgress_RunWorkerCompleted); } private void bwProgress_DoWork(object sender, DoWorkEventArgs e) {

www.openclassrooms.com

Les Threads en .NET


int i = 0; //Tant et aussi longtemps que la barre n'a pas atteint le 100% et qu'on //ne demande pas annuler... while (i < 100 && !bwProgress.CancellationPending) { //On attend 150ms. Thread.Sleep(150); i + 3. //On retrouve la valeur la plus petite entre 100 et i = Math.Min(100, i + 3); //On rapporte le progrs fait. bwProgress.ReportProgress(i);

29/31

private void btnStart_Click(object sender, EventArgs e) { //Le bouton joue le rle de dmarrage comme d'annulation selon la situation. if (!_etat) { bwProgress.RunWorkerAsync(); btnStart.Text = "Annuler"; } else { bwProgress.CancelAsync(); btnStart.Text = "Dmarrer"; } } _etat = !_etat;

private void bwProgress_ProgressChanged(object sender, ProgressChangedEventArgs e) { //On fait avancer la ProgressBar. pgProgress.Value = e.ProgressPercentage; } private void bwProgress_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //Lorsque c'est termin, on affiche un message indiquant la fin de l'activit. MessageBox.Show("Le BackgroundWorker a termin"); } } }

Tout ce qui a t dfini dans le constructeur du formulaire aurait pu tre mis dans le Designer graphique. Je l'ai mis l pour vous montrer quelles proprits et vnements ont t affects.

Donc avec le BackgroundWorker, le multi-tche en Windows Forms devient trs facile comme vous pouvez le voir. Tous les vnements appels seront excuts dans le thread principal, liminant ainsi l'utilisation de la commande Invoke. Magique n'est-ce pas ?

Cela conclut ce tutoriel. Il y a plusieurs parties des threads et des delegates que je n'ai pas couvert parce que je ne les considre pas ncssaires. Allez faire un tour du ct de MSDN pour plus d'explications et d'exemples ! Je vous met sur quelques pistes, comme la programmation rseau, les mthodes anonymes et les impressions en threads spars.

www.openclassrooms.com

Les Threads en .NET

30/31

Il n'y a jamais de fin ce que le .NET peut faire ! Les threads n'taient que la pointe du iceberg. Visitez MSDN si vous dsirez plus d'information et si vous vous posez une question sans rponse, je vous rappelle qu'il y a un Forum sur le Site du Zro pour demander.

Partager
Ce tutoriel a t corrig par les zCorrecteurs.

www.openclassrooms.com

Vous aimerez peut-être aussi