Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Les nouveaux chemins vers l’asynchronisme – II

Poster un commentaire

Plusieurs pattern d’asynchronisme ont été définis au fil du temps. Les utiliser n’est bien sûr pas obligatoire, mais ils aident à homogénéiser le développement.

Le point important à noter est qu’ils sont indépendants de l’environnement de développement, c’est ce qui en font un pattern. Nous allons en analyser 3 d’usage courant dans le monde .Net:

  1. APM
    Asynchonous Programing Model
  2. EAP
    Event base Asynchronous Pattern
  3. TAP
    Task Asynchronous Pattern

.Net propose de nombreux outils pour gérer ces patterns: Thread, Task, Monitor. Les patterns en imposent aucun de particulier, c’est à nous de choisir.

Le pattern APM

On parle aussi de pattern Begin/End.

Ce pattern s’applique à toutes les versions de .Net depuis la 1.0. Il est donc ancien, bien documenté, très utilisé et en pratique il est souvent trouvé prêt à l’emploi dans les classes .Net. Le principal inconvénient est que ce pattern n’est pas adapté au lancement concurrentiel de la tâche en asynchrone.

Au cœur de ce pattern se trouve IAsyncResult.

Les caractéristiques du pattern sont:

  1. une méthode Begin qui produit un IAsyncResult
  2. Une méthode End qui consomme le IAsyncResult

 

IAsyncResult propose 3 techniques possibles de rendez-vous:

  1. Wait Until
  2. Polling
  3. Callback

2014-10-03_15-31-57

Voyons une démonstration de ces 3 techniques. Il y a pas mal de code je l’ai donc poussé dans Github (projet ApmPattern de la solution):

https://github.com/DeLeneMirouze/Asynchronisme.git

Je vais me contenter de commenter les points importants. La démo consiste à lire de façon asynchrone le contenu d’un fichier. La classe FileStream fournit nativement une implémentation d’APM. C’est souvent le cas. Si vous avez besoin d’en implémenter un complètement vous pouvez lire ceci:

http://www.codeproject.com/Articles/37244/Understanding-the-Asynchronous-Programming-Model

Wait until fonctionne ainsi:

using (FileStream fs = new FileStream(fichier, FileMode.Open, FileAccess.Read, FileShare.Read, 1024, FileOptions.Asynchronous))
{
    buffer = new byte[fs.Length];

    Console.WriteLine("Démonstration de la technique par Wait-Until-Done");
    iAsyncResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);

    Console.WriteLine("Ici je fais des trucs important");
    // l'exécution se bloque jusqu'à ce que la méthode asynchrone se termine
    numBytes = fs.EndRead(iAsyncResult);
    Console.WriteLine("1: Lecture terminée, lu {0}  octets (Wait-Until-Done)", numBytes);

    Console.WriteLine("-----------------------------------------------------------");
    Console.WriteLine();
}

Peu de choses à dire sur ce code qui n’est pas très complexe. Notez que l’appel à la méthode End est synchrone. Il s’agit donc du point « wait » du pattern. On pourrait très bien placer du code applicatif entre le Begin et le End afin de lancer un traitement pendant que la lecture du fichier se poursuit en tâche de fond. L’affichage devrait ressembler à ceci:

2014-10-03_16-57-14

La méthode suivante par polling est rarement exploitée car très contraignante.

bool display = false;
using (FileStream fs = new FileStream(fichier, FileMode.Open, FileAccess.Read, FileShare.Read, 1024, FileOptions.Asynchronous))
{
    buffer = new byte[fs.Length];

    Console.WriteLine("Démonstration de la technique par polling");
    iAsyncResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);

    // on ne met pas de thread en attente, mais on vérifie périodiquement que le thread asynchrone soit terminé
    while (!iAsyncResult.IsCompleted)
    {
        // tant que ce n'est pas terminé
        // on peut effectuer ici des tâches importantes

        if (!display)
        {
            Console.WriteLine("Tâche importante ici");
            display = true;
        }
    }
    numBytes = fs.EndRead(iAsyncResult);
    Console.WriteLine("2: Lu {0}  octets (Polling)", numBytes);

    Console.WriteLine("-----------------------------------------------------------");
    Console.WriteLine();
}

On surveille le passage à true du drapeau IsCompleted. A ce moment là la tâche asynchrone est terminée. L’appel à la méthode End est nécessaire car c’est ici que les éventuelles exceptions levées par la tâche vont se déclencher. Si vous omettez cet appel des résultats imprévisibles et des fuites de mémoire peuvent apparaître.

2014-10-03_16-59-37

La méthode par callback est la plus intéressante et c’est celle que l’on utilise en général. Son principal avantage est qu’elle n’a pas besoin de mettre un thread en attente des tâches asynchrones.

using (FileStream fs = new FileStream(fichier, FileMode.Open, FileAccess.Read, FileShare.Read, 1024, FileOptions.Asynchronous))
{
    buffer = new byte[fs.Length];

    Console.WriteLine("Démonstration de la technique par rappel (callback)");
    iAsyncResult = fs.BeginRead(
        buffer,
        0,
        buffer.Length,
        new AsyncCallback(Callback), // méthode de rappel
        fs // la méthode de rappel aura besoin d'accéder à cette ressource, on la passe ici
    );
}

Console.WriteLine("Tâche importante ici");

// on attend que la tâche asynchrone se termine
//
// comprenez bien ce que le ReadLine fait ici. Il est nécessaire UNIQUEMENT parce que nous somme dans la méthode Main
// si on ne stoppe pas Main en attendant que le thread asynchrone se termine, alors l'application toute entière se terminera
// et on aura plus grand chose à voir!
// Dans le cas le plus usuel où la méthode asynchrone est appelée à des endroits plus profonds du code, on a juste besoin de
// laisser l'exécution se poursuivre et passer à la suite. Le thread sera juste rendu au pool de thread et rien ne sera bloqué
Console.ReadLine();

Avec la méthode par callback on fournit lors de l’appel à Begin une méthode de rappel qui sera appelée une fois la tâche terminée. C’est le paramètre de type AsyncCallback. La méthode est la suivante:

public static void Callback(IAsyncResult ar)
{
    FileStream fs = (FileStream)ar.AsyncState;
    int numBytes = fs.EndRead(ar); // on appelle bien EndRead, c'est important

    Console.WriteLine("3: Lu: {0}  octets", numBytes);

    // on n'est pas obligé d'utiliser une méthode de callback, on pourrait appeler EndRead() directement dans le code
    // au moment où l'on a  besoin du résultat de la méthode asynchrone.
   // EndRead() attend que la méthode soit terminée, on passe donc de l'asynchrone au synchrone
}

Ici aussi il ne faudra pas oublier l’appel à End pour les raisons évoquées précédemment.

2014-10-03_17-08-26

Dans le projet vous trouverez une petite variante de la méthode par callback avec un délégué anonyme. Il y a une petite subtilité.

Le pattern EAP

Ce pattern est arrivé avec .Net 2.0. Les caractéristique du pattern sont:

  1. La classe expose la méthode synchrone Methode1
  2. Elle expose également une méthode Methode1Async qui attend les même paramètres que Methode1, mais elle est obligatoirement void.
    C’est la version asynchrone de Methode1
  3. On expose un événement Methode1Completed appelé lorsque la méthode asynchrone sera appelée.

Ce pattern est capable de lancer plusieurs fois la même opération en mode concurrentiel ce que ne sait pas faire APM. Il est par contre un peu plus complexe et sur le fond assez peu employé. En fait son intérêt pratique a toujours fait l’objet de débats.

Toujours est t’il que .Net fournit des implémentations pour certaines classes comme:

Classe Opération
WorkflowInvoker InvokeAsync
BackgroundWorker RunWorkerAsync
SmtpClient SendAsync
Ping SendAsync
WebClient DownloadStringAsync

On va trouver une démonstration appelée EapPattern dans le projet GitHub.

Je ne crois pas utile de passer beaucoup de temps dessus, le projet de démo est largement documenté.

L’application lance en parallèle deux fois une méthode asynchrone et affiche une trace des exécutions. Une sortie typique sera:

2014-10-03_22-41-16

 

 

 

 

Le pattern TAP est un gros morceau car il fait appel à plusieurs notions nouvelles. Nous le verrons à l’œuvre dans les articles qui suivront.

 

 

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