• Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 29/04/2014

Une application fluide = une application propre !

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

La performance est un point crucial à prendre en compte lors du développement d’applications pour Windows Phone. Les ressources du téléphone sont beaucoup moins importantes que nos PC de développement. Aussi, il est important d’y faire attention afin de faire en sorte que son application soit réactive et ne paraisse pas bloquée. Vous devez bien sûr veiller à ce que vos algorithmes soient un minimum optimisés et ne pas vous dire « oh, ce n’est pas grave, le processeur du téléphone va m’optimiser tout ça… ». De même, vous devez comprendre le mécanisme des threads pour pouvoir tirer le meilleur de votre Windows Phone et obtenir l’application la plus fluide possible.

Un thread, c’est quoi ?

On peut traduire Thread par « fil d’exécution » ou « tâche ». Il s’agit d’un processus qui peut exécuter du code dans notre application, en l’occurrence le thread principal est celui que nous utilisons pour exécuter notre code C#. Il y aussi le thread principal d’interface, que l’on nomme UI Thread. Windows Phone possède un autre thread en relai du principal, c’est le thread de composition, appelé Composition Thread (ou Compositor Thread).

D’autres threads sont à notre disposition, ce sont des threads qui tournent en arrière-plan, de manière asynchrone. Nous nous en sommes déjà servis sans le savoir en utilisant la programmation asynchrone, par exemple lorsque nous téléchargeons des données avec les classes WebClient ou HttpWebRequest. Ces opérations asynchrones se font sur un thread secondaire.

Le thread d'interface

Le thread d’interface (UI Thread) va servir à mettre à jour l’interface ; ce qu’on voit à l’écran. En l’occurrence, il va servir à créer les objets depuis le XAML et dessiner tous les contrôles. Il gère également toutes les interactions avec l’utilisateur, notamment tous les touchers. Il est donc très important que ce thread soit le moins chargé possible afin que l’application reste réactive, notamment aux actions de l’utilisateur. Si ce thread contient une longue série de codes à exécuter, alors l’interface sera bloquée et l’utilisateur ne pourra plus rien faire, ce qui est fortement déplaisant et risque de le faire très vite désinstaller votre application…
Essayez plutôt de répartir les tâches, en imaginant que vous avez deux boutons dans votre page : l’un qui fait une action longue et l’autre qui affiche simplement un message dans une boîte. Voici le code de la boîte contenant les deux boutons :

<StackPanel>
    <Button Content="Lancer le calcul" Tap="Button_Tap" />
    <Button Content="Cliquez-moi" Tap="Button_Tap_1" />
</StackPanel>

Voici le code-behind des deux boutons :

private void Button_Tap(object sender, RoutedEventArgs e)
{
    List<int> nombrePremiers = new List<int>();
    for (int i = 0; i < 2000000; i++)
    {
        if (EstNombrePremier(i))
            nombrePremiers.Add(i);
    }
}

private void Button_Tap_1(object sender, RoutedEventArgs e)
{
    MessageBox.Show("Clic");
}

private bool EstNombrePremier(int nombre)
{
    if ((nombre % 2) == 0)
        return nombre == 2;
    int racine = (int)Math.Sqrt(nombre);
    for (int i = 3; i <= racine; i+=2)
    {
        if (nombre % i == 0)
            return false;
    }
    return nombre != 1;
}

Le premier bouton permettra de déterminer les nombres premiers de 0 jusqu’à 2000000, le deuxième affichera un simple message, dans une boîte de dialogue.
Si vous démarrez l’application et cliquez sur le premier bouton, vous ne pourrez pas cliquer sur le deuxième bouton tant que le premier calcul n’est pas terminé. De plus, on voit à l’état du bouton que celui-ci reste cliqué tant que le traitement long n’est pas terminé.
Nous avons donc bloqué le thread UI en effectuant un calcul trop long. De ce fait, l’application n’est plus capable de traiter correctement les entrées utilisateurs, comme le clic sur le deuxième bouton, étant donné que le thread UI est surchargé par le long calcul.
Afin que le code soit plus court, nous allons remplacer le long calcul par une mise en veille du thread courant grâce à la méthode Thread.Sleep(), que nous retrouverons dans l’espace de noms :

using System.Threading;

Ceci nous permet de simuler un traitement long tout en économisant des lignes de codes, d’où le code-behind devient :

private void Button_Tap(object sender, RoutedEventArgs e)
{
    Thread.Sleep(TimeSpan.FromSeconds(4));
}

private void Button_Tap_1(object sender, RoutedEventArgs e)
{
    MessageBox.Show("Clic");
}

Ceci me permet de simuler un traitement qui dure 4 secondes.

Ok, c’est bien beau, mais notre interface semble toujours bloquée et incapable de traiter le clic sur le deuxième bouton.

Utiliser un thread d’arrière-plan

Une solution pour résoudre ce problème serait d’utiliser un thread d’arrière-plan. Ce ne sera donc plus le thread UI qui va gérer le calcul mais un thread qui tourne en arrière-plan. C’est un peu le même principe qu’avec une opération asynchrone, comme lorsque nous effectuions un téléchargement, notre code qui s’exécute utilisera une partie de la mémoire pour fonctionner de manière plus ou moins parallèle au thread UI, ce qui lui permettra de continuer à pouvoir traiter les actions de l’utilisateur. On pourra utiliser pour cela la classe Thread. Il suffit d'inclure une méthode dans le thread (ici je passe une expression lambda, qui le met en pause pendant 4 secondes), d'appeler la méthode Start, et Windows Phone s'occupera d'exécuter notre méthode dans un thread d'arrière-plan. Cela donnera :

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void Button_Tap(object sender, RoutedEventArgs e)
    {
        Thread thread = new Thread(() => Thread.Sleep(TimeSpan.FromSeconds(4)));
        thread.Start();
    }

    private void Button_Tap_1(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Clic");
    }
}

Maintenant, si vous démarrez votre application, vous pourrez voir que l’exécution du code long ne bloque plus le traitement du clic sur l’autre bouton. Ô joie, merci les threads !
En fait, notre code est un peu bête ! On fait des calculs mais ils ne nous servent à rien ici … Je suis sûr que, comme moi, vous seriez très curieux de connaître le plus grand nombre premier inférieur à 10 millions, n’est-ce pas ? Ok, ressortons notre méthode, utilisons notre thread et affichons le résultat dans un TextBlock :

<StackPanel>
    <Button Content="Lancer le calcul" Tap="Button_Tap" />
    <Button Content="Cliquez-moi" Tap="Button_Tap_1" />
    <TextBlock x:Name="Resultat" />
</StackPanel>

Le calcul sera fait ainsi :

private void Button_Tap(object sender, RoutedEventArgs e)
{
    Thread thread = new Thread(() => 
        {
            int max = 0;
            for (int i = 0; i < 10000000; i++)
            {
                if (EstNombrePremier(i))
                    max = i;
            }
            Resultat.Text = "Résultat : " + max; 
        });
    thread.Start();
}

Sauf que, si vous démarrez l’application, que vous lancez le calcul, votre application va se mettre à planter avec une belle erreur du nom de UnauthorizedAccessException (que l'on peut apercevoir dans la figure qui suit).

Levée d'exception lors de l'accès au TextBlock
Levée d'exception lors de l'accès au TextBlock

Pourquoi cette erreur ?

Pour une simple et bonne raison et je crois qu’on peut la mettre en avertissement :

Utiliser le Dispatcher

Nous avons déjà rapidement vu comment résoudre ce problème lorsque nous avons utilisé des opérations asynchrones (HttpWebRequest et WebClient) et que nous avons dû mettre à jour l’interface. Nous avons résolu le problème grâce au Dispatcher.

Ce dispatcher permet d’exécuter des actions sur le thread auquel il est associé, grâce à sa méthode BeginInvoke. En l’occurrence, chaque DependyObject possède un dispatcher et donc, la PhoneApplicationPage possède également un dispatcher par héritage, qui a été créé depuis le thread UI. Ainsi, l’appel à BeginInvoke depuis un thread d’arrière-plan sur le dispatcher de la page exécutera automatiquement l’action sur le thread UI. Voyons ceci dans le code et le résultat illustré suivants.

private void Button_Tap(object sender, RoutedEventArgs e)
{
    Thread thread = new Thread(() => 
        {
            int max = 0;
            for (int i = 0; i < 10000000; i++)
            {
                if (EstNombrePremier(i))
                    max = i;
            }
            Dispatcher.BeginInvoke(() => Resultat.Text = "Résultat : " + max); 
        });
    thread.Start();
}

Ce qui donne :

Affichage correct grâce au Dispatcher
Affichage correct grâce au Dispatcher

Et voilà, plus de problèmes d’accès interdit entre les threads.

Utiliser un BackgroundWorker

Je vous ai dit qu’utiliser directement la classe Thread n’était pas la solution la plus élégante pour créer des threads. C’est vrai (je ne mens jamais) ! Il existe d’autres classes particulièrement adaptées pour réaliser des traitements longs sur un thread d’arrière-plan, c’est le cas par exemple de la classe BackgroundWorker. Elle permet notamment de connaître l’état d'avancement du thread, et de l'annuler si besoin. Pour l’utiliser, vous aurez besoin d’inclure l’espace de nom suivant :

using System.ComponentModel;

Vous aurez également besoin d’avoir une variable représentant le BackgroundWorker. En général, on utilise une variable membre de la classe :

private BackgroundWorker bw;

Au moment d’instancier le BackgroundWorker, il faudra lui passer les paramètres dont il a besoin.

public MainPage()
{
    InitializeComponent();

    bw = new BackgroundWorker { WorkerSupportsCancellation = true, WorkerReportsProgress = true };
    bw.DoWork += bw_DoWork;
    bw.ProgressChanged += bw_ProgressChanged;
    bw.RunWorkerCompleted += bw_RunWorkerCompleted;
}

Ici, nous lui indiquons qu’il est autorisé d’annuler le thread et que celui-ci peut témoigner de son avancement.
Ensuite nous déclarons des événements. DoWork contiendra le long traitement à effectuer. ProgressChanged sera un événement levé lorsque le thread témoigne de son avancement. Enfin, RunWorkerCompleted sera levé lorsque le thread aura terminé son travail.
Voyons la méthode de travail :

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = (BackgroundWorker)sender;

    int max = 0;
    int derniereValeur = 0;
    for (int i = 0; i < 10000000; i++)
    {
        if (worker.CancellationPending)
        {
            e.Cancel = true;
            break;
        }
        int pourcentage = i * 100 / 10000000;
        if (pourcentage != derniereValeur)
        {
            derniereValeur = pourcentage;
            worker.ReportProgress(pourcentage);
        }
        if (EstNombrePremier(i))
            max = i;
    }
    e.Result = max;
}

On peut retrouver notre long calcul des nombres premiers. On y trouve également plusieurs choses, comme de vérifier s’il n’aurait pas été demandé à notre thread de se terminer prématurément. Cela se fait en testant la propriété CancellationPending. Si elle vaut vrai, alors on peut arrêter le calcul et marquer le BackgroundWorker comme étant annulé, grâce à l’argument de l’événement.
Ensuite, la méthode ReportProgress nous offre l’opportunité d’indiquer l’avancement du calcul. Ici, je lui indique le pourcentage d’avancement par rapport au max à calculer. Remarquez que je n’appelle la méthode ReportProgress que si le pourcentage d’avancement a changé car sinon je l’appellerai énormément de fois inutilement, ce qui ralentirait considérablement mon calcul.
Enfin, je peux utiliser la propriété Result pour stocker le résultat de mon calcul.

Vous avez donc compris que l’événement ProgressChanged était levé lorsque nous appelions la méthode ReportProgress. Cela nous permet de mettre à jour un affichage par exemple pour montrer l’avancement du thread :

private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    Resultat.Text = e.ProgressPercentage + "%";
}

Notez qu’ici, il n’y a pas besoin de Dispatcher pour mettre à jour l’interface, malgré l’utilisation d’un thread d’arrière-plan. Tout cela est géré par le BackgroundWorker
Enfin, il reste la méthode qui est appelée lorsque le calcul est terminé, RunWorkerCompleted :

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
        Resultat.Text = "Vous avez annulé le calcul !";
    else if (e.Error != null)
        Resultat.Text = "Erreur : " + e.Error.Message;
    else
        Resultat.Text = "Fini " + e.Result;
}

Ici, nous avons plusieurs cas de figure :

  • Le thread a été annulé. Dans ce cas, la propriété Cancelled de l’argument est Vraie (True).

  • Il y a une erreur. Dans ce cas, la propriété Error est différente de null.

  • Ou alors tout s’est bien passé. Dans ce cas, on peut récupérer le résultat dans la propriété Result.

Comment annuler le thread ? Modifions notre XAML pour ajouter un bouton dont le rôle sera de stopper le thread :

<StackPanel>
    <Button Content="Lancer le calcul" Tap="Button_Tap" />
    <Button Content="Arrêter le calcul" Tap="Button_Tap2" />
    <Button Content="Cliquez-moi" Tap="Button_Tap_1" />
    <TextBlock x:Name="Resultat" />
</StackPanel>

Le code associé pour arrêter un thread est simplement :

private void Button_Tap2(object sender, System.Windows.Input.GestureEventArgs e)
{
    if (bw.WorkerSupportsCancellation)
        bw.CancelAsync();
}

On utilise la méthode CancelAsync.
Pour démarrer le calcul, c’est le même principe, il suffit d’utiliser la méthode RunWorkerAsync :

private void Button_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
    if (!bw.IsBusy)
        bw.RunWorkerAsync();
}

Et voilà. Lorsque vous démarrerez le calcul, non seulement l’interface ne sera pas bloquée, mais vous pourrez également voir l’avancement du thread ainsi que l’arrêter en cours de route (voir figure suivante).

Thread en cours d'exécution puis arrêté
Thread en cours d'exécution puis arrêté

Enchaîner les threads dans un pool

Une autre solution pour démarrer des threads consiste à utiliser la classe ThreadPool. Elle est très pratique lorsque nous avons besoin d’enchainer beaucoup de traitements d’arrière-plan. Grâce au pool de thread nous pourrons empiler les différents threads que nous souhaitons exécuter en arrière-plan, et c’est le système qui va se débrouiller pour les enchaîner les uns après les autres ou potentiellement en parallèle, sans que nous n'ayons quelque chose de particulier à faire.
C’est très pratique. Imaginons par exemple une application qui doit lire beaucoup de flux RSS. Si nous démarrons tous les téléchargements en même temps, il y a fort à parier que nous allons obtenir un timeout. Dans ce cas, la bonne solution est de pouvoir les enchaîner séquentiellement. C’est un parfait candidat pour un ThreadPool.
Pour l’illustrer, nous allons faire un peu plus simple et empiler des threads qui auront une durée d’exécution aléatoire :

private void Button_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
    Random random = new Random();
    for (int i = 0; i < 10; i++)
    {
        int numThread = i;
        ThreadPool.QueueUserWorkItem(o =>
        {
            int temps = random.Next(1, 10);
            Thread.Sleep(TimeSpan.FromSeconds(temps));
            Dispatcher.BeginInvoke(() => Resultat.Text += "Thread " + numThread + " terminé en " + temps + " secs" + Environment.NewLine);
        });
    }
}

Rien de bien compliqué, mais il y a cependant une petite astuce pour utilisateurs avancés. Il s’agit de l’utilisation de la variable numThread. On pourrait croire qu’elle ne sert à rien et qu’on pourrait utiliser juste la variable i à la place, mais que nenni. Si vous l’enlevez, vous n'aurez que des threads numérotés 10. Tout ça, à cause d’une histoire de closure que je vais vous épargner, mais si vous êtes curieux, vous pouvez trouver une explication en anglais ici.
Vous trouverez le résultat en images dans la prochaine figure.

L'exécution des threads a été prise en charge par le pool de thread
L'exécution des threads a été prise en charge par le pool de thread

Le DispatcherTimer

Nous avons déjà utilisé cette classe précédemment mais sans l’avoir vraiment décrite. La classe DispatcherTimer va nous permettre d’appeler une méthode à intervalles réguliers. L’intérêt va être de pouvoir exécuter une tâche en arrière-plan et de manière répétitive. Imaginons par exemple une tâche de synchronisation, qui toutes les 5 minutes va enregistrer le travail de l’utilisateur sur le cloud …
L’exemple le plus classique est la création d’une horloge, dont l’heure est mise à jour toutes les secondes :

<TextBlock x:Name="Heure" />

La mise à jour de ce contrôle se fera périodiquement grâce au DispatcherTimer :

private DispatcherTimer dispatcherTimer;

public MainPage()
{
    InitializeComponent();

    dispatcherTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
    dispatcherTimer.Tick += dispatcherTimer_Tick;
    dispatcherTimer.Start();
}

private void dispatcherTimer_Tick(object sender, EventArgs e)
{
    Heure.Text = DateTime.Now.ToString();
}

Voilà notre TextBlock qui est mis à jour toutes les secondes.
L’avantage de ce timer c’est qu’il utilise la file d’attente du dispatcher, donc nul besoin d’utiliser le Dispatcher pour pouvoir mettre à jour l’interface, ceci est pris en charge automatiquement. L’inconvénient, c’est que rien ne nous garantit que la méthode soit effectivement appelée exactement toutes les secondes. Vous pourrez observer quelques variations en fonction de l’existence d’autres timers ou d’éléments dans le Dispatcher. Si vous laissez tourner un peu votre application, vous verrez que, de temps en temps, il se décale d’une seconde, ce qui n’est pas dramatique, et au pire, nous pouvons augmenter la fréquence de mise à jour.
Mais le timer va nous permettre aussi d’illustrer un autre point important de Windows Phone, utilisons-le par exemple pour faire bouger un rectangle sur notre écran. Le XAML est le suivant :

<StackPanel>
    <Button Content="Lancer le calcul" Tap="Button_Tap" />
    <Canvas>
        <Rectangle x:Name="Rect" Width="40" Height="40" Fill="Green" />
    </Canvas>
</StackPanel>

Voici le code-behind correspondant :

public partial class MainPage : PhoneApplicationPage
{
    private DispatcherTimer dispatcherTimer;
    private int direction = 1;

    public MainPage()
    {
        InitializeComponent();

        dispatcherTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(10) };
        dispatcherTimer.Tick += dispatcherTimer_Tick;
        dispatcherTimer.Start();
    }

    private void dispatcherTimer_Tick(object sender, EventArgs e)
    {
        double x = (double)Rect.GetValue(Canvas.LeftProperty);
        int pas = 10;
        if (x > 480 - Rect.Width)
            direction = -1;
        if (x < 0)
            direction = 1;
        x += pas * direction;
        Rect.SetValue(Canvas.LeftProperty, x);
    }

    private void Button_Tap(object sender, System.Windows.Input.GestureEventArgs e)
    {
        Thread.Sleep(TimeSpan.FromSeconds(5));
    }
}

Si vous démarrez l’application, vous pourrez voir le rectangle qui s’anime horizontalement. Ceci pourrait être une bonne solution pour animer un objet, sauf que … cliquez voir un peu sur le bouton qui bloque le thread UI en simulant un long calcul … et paf ! Ça ne bouge plus.
Ah bravo ! Franchement, ça ne se fait pas de bloquer le thread UI !!!

Thread de composition

Vous me direz, il suffit de mettre le calcul dans un thread et je vous répondrai que oui, cela fonctionne sans problème. Mais il y a d’autres solutions pour faire en sorte qu’une animation fonctionne malgré un long calcul. Vous vous rappelez comment on définit une animation via un StoryBoard ?

<StackPanel>
    <Button Content="Lancer le calcul" Tap="Button_Tap" />
    <Canvas>
        <Canvas.Resources>
            <Storyboard x:Name="MonStoryBoard">
                <DoubleAnimation From="0" To="440" Duration="0:0:2" RepeatBehavior="Forever" AutoReverse="True"
                    Storyboard.TargetName="Rect"  Storyboard.TargetProperty="(Canvas.Left)"/>
            </Storyboard>
        </Canvas.Resources>
        <Rectangle x:Name="Rect" Width="40" Height="40" Fill="Green" />
    </Canvas>
</StackPanel>

Puis dans le code-behind :

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();

        MonStoryBoard.Begin();
    }

    private void Button_Tap(object sender, System.Windows.Input.GestureEventArgs e)
    {
        Thread.Sleep(TimeSpan.FromSeconds(5));
    }
}

Et là, lorsque vous démarrez l’application, le fait de bloquer le thread UI ne bloque pas l’animation. Diantre, comment cela se fait-il ?
C’est grâce au thread de composition !

Le thread de composition va servir à alléger le thread UI. Il va pouvoir décharger le thread UI de certaines tâches qui lui incombent. Alors que le thread UI va être utilisé pour créer le premier rendu des contrôles, le thread de composition va travailler directement avec ces éléments qui sont mis en cache dans la mémoire sous forme d’images bitmaps et utiliser en préférence le processeur graphique. Les animations par exemple sont prises en charge par ce thread de composition et mises en cache pour un affichage rapide. Le processeur graphique étant indépendant il pourra continuer à traiter des informations même si le processeur du téléphone est fortement sollicité. Lorsque vous demandez la mise à jour d’un contrôle qui nécessite un changement (changement de couleur, taille, …) alors ce contrôle est supprimé du cache et redessiné par le thread UI.
Cette utilisation de cache apporte beaucoup de performance et de plus, certaines des opérations peuvent être faites directement sur les objets en cache, par exemple une animation qui fait une rotation.
Comme nous l’avons vu, la meilleure solution pour corriger l’animation précédente est de créer l’animation en XAML et d’utiliser les contrôles adéquats afin qu’elle soit prise en charge par ce fameux thread de composition et utilise mécanisme de mise en cache. C’est toujours le thread UI qui est en charge de mettre à jour l’interface, sauf qu’il ne fait plus aucun calcul et affiche directement des bitmaps mis en cache. C’est le thread de composition qui fait les calculs en s’aidant du processeur graphique, qui est particulièrement doué pour ça.
D’une manière générale, il vaut mieux laisser le système gérer lui-même le cache, mais dans certains cas il peut être utile de positionner soi-même la propriété CacheMode d'un objet BitmapCache sur un contrôle, mais il vaut mieux savoir ce que l’on fait et tester les performances avec ou sans, car le cache augmente considérablement la consommation de mémoire des processeurs du téléphone.

Vous vous rappelez de l'application PerformanceProgressBar pour Windows Phone 7.5 ou de l'application ProgressBar pour Windows Phone 8 ? Leur principe est justement de faire en sorte que ce soit le thread de composition qui prenne en charge l’animation et non le thread UI.

Les outils pour améliorer l’application

Il existe plusieurs outils pour améliorer la réactivité de son application.
Il y a notamment le Windows Phone Performance Analysis tool, qui est un outil de profilage qui s’installe avec l'environnement de développement SDK de Windows Phone. Il permet de mesurer la consommation de mémoire du processeur central CPU (Central Processing Unit), mais aussi le nombre de rafraîchissements par seconde (le frame-rate). Je ne vais pas le décrire ici car on sort un peu de l’apprentissage de Windows Phone, mais sachez que cet outil existe et qu’il peut vous aider à optimiser vos applications.
Pour une description, en anglais, vous pouvez consulter : http://msdn.microsoft.com/en-us/librar [...] =vs.105).aspx.

Vous avez également à votre disposition des compteurs. Ce sont ces compteurs que l’on voit apparaître dans notre émulateur sur la droite. Ils permettent de connaître entres autres le taux de rafraîchissement. Plus d’informations ici http://msdn.microsoft.com/fr-fr/librar [...] v=vs.92).aspx.

Vous avez aussi la possibilité de voir facilement quels sont les contrôles qui sont mis à jour. Il suffit de positionner une variable booléenne précise et vous verrez dans vos applications s’il y a un contrôle qui se met à jour alors qu’il ne le devrait pas. Voir à cet emplacement :
http://msdn.microsoft.com/fr-fr/librar [...] v=VS.95).aspx.

  • Les threads sont importants à maîtriser afin d'avoir une application fluide et réactive.

  • Il n'est pas possible de mettre à jour l'interface depuis un thread d'arrière-plan. On utilisera le Dispatcher.

  • Il existe des classes puissantes du framework .NET pour gérer les threads d'arrière-plan.

Exemple de certificat de réussite
Exemple de certificat de réussite