Explorer les Livres électroniques
Catégories
Explorer les Livres audio
Catégories
Explorer les Magazines
Catégories
Explorer les Documents
Catégories
NET
Par gretro
www.openclassrooms.com
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
www.openclassrooms.com
Sommaire
3/31
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
www.openclassrooms.com
4/31
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
5/31
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.
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 .
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
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
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 ...
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
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 !
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
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.
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;
www.openclassrooms.com
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
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
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.
www.openclassrooms.com
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.
V oici le rsultat :
www.openclassrooms.com
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
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
16/31
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
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
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);
www.openclassrooms.com
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
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
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
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
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;
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();
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
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.
www.openclassrooms.com
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
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
27/31
100); try...catch !
//On invoque le delegate pour qu'il effectue la //de l'autre thread. Invoke((MontrerProgres)Progres, valeur);
} }
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
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
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
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