Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Les nouveaux chemins vers l’asynchronisme – III

Poster un commentaire

Après le tour d’horizon effectué lors des deux articles qui précèdent:

  1. https://amethyste16.wordpress.com/2014/10/02/les-nouveaux-chemins-vers-lasynchronisme-i/
  2. https://amethyste16.wordpress.com/2014/10/03/les-nouveaux-chemins-vers-lasynchronisme-ii/

Nous entrons dans le vif du sujet. Microsoft propose une façon très différente de prendre en charge l’asynchronisme: les Task.

Une Task est simplement un bloc de code que l’on exécute de façon asynchrone. Il s’agit donc d’un concept beaucoup plus simple que le Thread. Sa mise en œuvre est elle-aussi beaucoup plus facile.

Contrairement aux threads, une Task n’effectue aucune action par elle-même. Elle est déposé dans une pile spéciale du ThreadPool et attend qu’un Thread lui soit affectée.

Si le pool de Thread présente un nombre limité d’instances (et il est peu conseillé de le modifier), on peut pousser autant de Task que l’on souhaite. Nous ne le verrons pas dans cet article, mais Microsoft a beaucoup travaillé sur les algorithmes d’affectation.

Fonctionnement général

Concrètement on pourrait représenter la situation ainsi. Imaginons une tranche de temps dans le cycle de vie d’une application faisant appel à des Task:

2014-10-19_17-48-35

Tout en bas on a représenté le thread principal ainsi que 4 Task. Ces Task sont déposées dans une pile spéciale du pool de threads.

Ce n’est pas le thread principal qui exécute les Task, mais les worker process issus du pool de threads. Nous en avons représenté un pool avec 3 processus de travail.

Examinons différentes situations:

  • Task 1
    Le pool attache un worker process à cette tâche qui a été déposé dans la pile auparavant. Le WP 2 est l’heureux élu et exécute entièrement la tâche
  • Task 2
    T2 a besoin de plus de temps pour s’exécuter que T1. Il est pris en charge par WP 1. On constate qu’à un certain moment, le pool détache WP 1 de la tâche et lui associe le WP 3. C’est WP 3 qui termine la tâche
  • Task 3
    T3 démarre son périple sur WP 3. Au bout d’un certain temps WP 3 est détaché de T3 et le pool lui confie les affaires de T2.
    T3 est en attente, il attend peut être qu’une ressource se libère.
    W3 a maintenant terminé avec T2 et T3 est prêt à poursuivre son exécution. W3 est attaché à nouveau à W3 par le pool.
  • Task 4
    Dans cette tranche de temps aucun WP n’a pu être attaché à T4

Lancement d’une Task, les bases

Passons à quelques exemples concrets de création de Task.

Supposons que l’on dispose de ces deux méthodes de tests:

private static void Meth1()
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    long compteur = 0;
    for (int i = 0; i < 1000000000; i++)
    {
        compteur += i;
    }

    sw.Stop();
    Console.WriteLine("Meth1: {0} ms", sw.ElapsedMilliseconds);
}

private static void Meth2(long limite)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    long compteur = 0;
    for (int i = 0; i < limite; i++)
    {
        compteur += i;
    }

    sw.Stop();
    Console.WriteLine("Meth2: {0} ms", sw.ElapsedMilliseconds);
}

private static long Meth3(long limite)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    long compteur = 0;
    for (int i = 0; i < limite; i++)
    {
        compteur += i;
    }

    sw.Stop();
    Console.WriteLine("Meth3: {0} ms", sw.ElapsedMilliseconds);

    return compteur;
}

 

Ces méthodes sont des méthodes absolument ordinaires, mais on peut les transformer en Task très simplement avec une des deux méthodes qui suivent:

Task t1 = new Task(Meth1);

Task t2 = new Task(() =>
    {
        Meth1();
    });

Placez un point d’arrêt dans Meth1 et vous constaterez que rien ne s’exécute. Pour lancer une tâche on doit ajouter ceci:

static void Main(string[] args)
{
    Task t1 = new Task(Meth1);
    Task t2 = new Task(() =>
    {
        Meth1();
    });

    t1.Start();
    t2.Start();

    Console.WriteLine("On attend");
    Task.WaitAll(t1, t2);
    Console.WriteLine("Terminé");

    Console.ReadLine();
}

Notez la présence des méthodes Start(). L’exécution de ce bout de code est instructif:

2014-10-19_18-21-23

Les méthodes Start n’attendent pas que les tâches soient terminées pour passer à la ligne suivante comme le démontre le premier message. C’est le rôle de WaitAll d’attendre que les taches se terminent.

Notez tout de même que dans cet exemple l’appel à WaitAll est inutile puisque la méthode ReadLine se charge d’attendre que les tâches terminent.

Task t1 = new Task(Meth1);
Task t2 = new Task(() =>
    {
        Meth1();
    });

    t1.Start();
    t2.Start();

    Console.WriteLine("Terminé");
    Console.ReadLine();
}

Entre Start et WaitAll l’application peut continuer à travailler sur autre chose pendant que les tâches s’exécutent en arrière-plan. C’est cela la programmation asynchrone.

Avant de passer à la suite notons d’autres syntaxes possibles:

Task t1 = new TaskFactory().StartNew(Meth1);
Task t2 = new TaskFactory().StartNew(() =>
    {
        Meth1();
    });

Task t3 = Task.Run(() =>
    {
        Meth1();
    });

Un des intérêts de ces méthodes est d’inclure l’appel à Start.

Une Task peut évidemment avoir un type de retour et des paramètres.

Task t1 = Task.Run(() =>
{
    long limite = GetLimite();
    Meth2(limite);
});
Task.WaitAll(t1);

Task<long> t2 = Task<long>.Run(() =>
{
    long limite = GetLimite();
    return Meth3(limite);
});
Task.WaitAll(t2);
Console.WriteLine("Retour: {0}", t2.Result);

 

GetLimite() est juste une méthode qui retourne un long.

Le premier test démontre le cas d’une méthode qui attend des paramètres. Peu de choses à dire, ce code est parfaitement normal.

Le deuxième test est plus intéressant. Nous utilisons une nouvelle surcharge pour Run qui tient compte du retour de Meth3. Le retour est transmit à la Task via sa propriété Result.

 

Maintenant que les bases sont en place, dans le prochain article on verra comment utiliser tout ça dans la vraie vie, puis on glissera doucement vers le pattern async/await.

Bibliographie

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s