Interface utilisateur multitâches

INTERFACE UTILISATEUR ET MULTITHREADING AVEC .NET

Introduction

Ce tutoriel présente deux classes : BackgroundWorker surtout et ThreadPool qui permettent de rendre l'interface utilisateur plus fluide, puisqu'elles gèrent les thread d'arrière-plan. Cas typique : la barre de progression (d'un téléchargement) qui ne gèle pas l'interface utilisateur puisqu'elle est gérée en arrière-plan.

Hervé Labénère - 16 nov 2009

Inspiré de la MSDN et du livre Advanced C# Programming de Paul Kimmel (MacCraw - 2002)

Exécuter une tâche en arrière plan : Backgroundworker

Pour gérer les tâches longues sans paralyser l'interface utilisateur, on utilisera en premier lieu une méthode relativement simple : la classe Backgroundworker qui gère les tâches (ou threads) d'arrière-plan.
Un thread d'arrière-plan est identique à un thread de premier plan, à l'exception près suivante : il ne bloque pas l'exécution de l'environnement.

private BackgroundWorker tacheArrierePlanMajBarreProgression; //1

private void btnLancement_Click(object sender, EventArgs e)
{
    tacheArrierePlanMajBarreProgression = new BackgroundWorker(); //2
    tacheArrierePlanMajBarreProgression.WorkerReportsProgress = true;
    tacheArrierePlanMajBarreProgression.WorkerSupportsCancellation = true;

    tacheArrierePlanMajBarreProgression.DoWork += delegate(object s, DoWorkEventArgs args) //3
    {
        BackgroundWorker tacheArrierePlanIncrementation = s as BackgroundWorker;
        for (int i = 0; i < 10; i++)
        {
            if (tacheArrierePlanIncrementation.CancellationPending)
            {
                args.Cancel = true;
                return;
            }

            Thread.Sleep(1000);
            tacheArrierePlanIncrementation.ReportProgress(i + 1);
        }
    };


    tacheArrierePlanMajBarreProgression.ProgressChanged += delegate(object s, ProgressChangedEventArgs args) //4
    {
        pgbTacheArrierePlan.Value = args.ProgressPercentage;
    };

    tacheArrierePlanMajBarreProgression.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
    {
        pgbTacheArrierePlan.Value = 0;
    };

    tacheArrierePlanMajBarreProgression.RunWorkerAsync();//5       
}

1. On déclare l'objet Backgroundworker
2. On initialise l'objet Backgroundworker, en précisant aussi qu'il doit signaler sa progression
3. Le Backgroundworker déclenche l'évènement DoWork lorsque la méthode RunWorkerAsync() est appelée. L'évènement DoWork se concrétise par une boucle de 0 à 10, avec une pause à chaque tour de 1s. Et le Backgroundworker qui incrément sa progression de 1, à chaque tour.
4. Chaque fois que l'évènement du Backgroundworker ProgressChanged est levé, il déclenche la mise à jour de la barre de progression.
5. On déclenche la tâche d'arrière-plan.

Autre méthode : ThreadPool

Dans l'exemple ci-dessous, il s'agit d'exécuter en tâche de fond une boucle incrémentale jusqu'à 100 millions, pendant que l'interface utilisateur reste disponible.
Il faudra donc faire du multithreading, grâce à l'objet ThreadPool, qui optimise la gestion des threads d'arrière-plan. On appelle la méthode QueueUserWorkItem, pour demander qu'une tâche Utilisateur soit gérée par le threadpool, dans notre cas la méthode LoadListBox().

private delegate void Add(int i); //1
private void AddItem(int i) //2
{
    listBox1.Items.Add(i);
    Application.DoEvents();
}

private void LoadListBox(object state) //3
{
    for (int i = 0; i < 100000000; i++)
        listBox1.Invoke(new Add(AddItem), new object[] { i });
}

private void Form1_Load(object sender, System.EventArgs e) //4
{
    System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(LoadListBox));
}

On commence par créer un type délégué Add, qui reflète la façon d'interagir avec le thread du formulaire. La signature de sa méthode prend un entier et ne retourne rien (Void)
Méthode AddItem interagissant avec la listBox
La méthode LoadListBox s'exécutant sur son propre thread
Lancement en tâche de fond de la méthode LoadListBox, grâce à la classe ThreadPool.QueueUserWorkItem

Le même programme avec la classe BackgroundWorker à la place de ThreadPool

private BackgroundWorker tacheArrierePlanMajListBox; //1

private delegate void Add(int i);

private void AddItem(int i)
{
    listBox1.Items.Add(i);
    Application.DoEvents();
}

private void Form1_Load(object sender, System.EventArgs e)
{
    tacheArrierePlanMajListBox = new BackgroundWorker(); //2
    tacheArrierePlanMajListBox.WorkerReportsProgress = true;
    tacheArrierePlanMajListBox.WorkerSupportsCancellation = true;

    tacheArrierePlanMajListBox.DoWork += delegate(object s, DoWorkEventArgs args) //3
    {
        for (int i = 0; i < 100000000; i++)
        {
            listBox1.Invoke(new Add(AddItem), new object[] { i });
        }
    };

    tacheArrierePlanMajListBox.RunWorkerAsync();//5        
}
Ce document intitulé « Interface utilisateur multitâches » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.