• Stéphanie Hertrich

    Différences entre la TPL et async/await par l'exemple

    • 3 Comments

     

    Entre nos machines multi-cores, nos services hébergés dans les nuages et nos interfaces graphiques souhaitées de plus en plus réactives, les questions touchant à l’asynchronisme et au parallélisme ne peuvent plus être reléguées au second plan.


    Ce sont des sujets officiellement  à la mode et c’est tant mieux ! :

    ·         La Task Parallel Library intégrée à .Net 4 offre un framework de plus haut niveau que les threads et permet de simplifier l’écriture de code parallèle.

    ·         La CTP Async pour Visual Studio 2010 est arrivée quant à elle très récemment et permet d’écrire plus simplement un appel asynchrone.

    Mais le code parallèle n’est-il pas asynchrone ? La TPL ne permet-elle pas de réaliser l’équivalent du couple async/await ?

    A travers un exemple tout simple, explorons les différentes possibilités de parallélisme et/ou asynchronisme que nous offrent la TPL et le couple async/await.

    Dans ce post, nous aborderons les sujets suivants:

    1. Appel des actions  en séquentiel
    2. Appel des actions en parallèle avec Parallel.Invoke()
    3. Appel des actions en parallèle avec Parallel.ForEach()
    4. Appel des actions en parallèle avec PLinq
    5. Appel des actions en parallèle et asynchrone avec l’objet Task
    6. Appel des actions en parallèle et asynchrone avec continuation
    7. Appel des actions en séquentiel asynchrone avec async/await
    8. Appel des actions en parallèle et asynchrone avec async/await

    Voici le code qui constitue la base de notre exemple, ce sont 3 actions qui attendent 1s, 2s et 3 secondes:

    [j’en profite pour définir les actions de 3 manières différentes, mais c’est sans incidence sur la suite de l’exemple]


    static void Action1()
    {
        Thread.Sleep(1000);
        Trace.WriteLine("\tFin Action1");
    }
     
    // ----------------------------------------------------------
    // Différentes manières de définir des actions
    // ----------------------------------------------------------
     
    // méthode nommée : Action1
    Action action1 = Action1;
     
    // méthode anonyme
    Action action2 = delegate()
    {
        Thread.Sleep(2000);
        Trace.WriteLine("\tFin Action2");
    };
     
    // lambda
    Action action3 =
        () => { Thread.Sleep(3000); Trace.WriteLine("\tFin Action3"); };

    [Je rappelle que le type Action est un delegate de type void]

    En résumé, le fonctionnel des actions est très simple :

    • action1 attend 1 seconde
    • action2 attend 2 secondes
    • action3 attend 3 secondes

     


    1. Appel des actions  en séquentiel

    Si j’appelle ces 3 actions (par-exemple déclenchées sur click d’un bouton) de manière classique, c’est à dire de manière séquentielle, j’écrirais le code suivant :

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Trace1();
        Sequentiel();
        Trace4();
    }
     
    // Appel synchrone des actions en séquentiel
    void Sequentiel()
    {
        Trace2();
     
        action1();
        action2();
        action3();
     
        Trace3();
    }

    Voici le code utilisé pour les traces, il n’est pas très important, excepté que Trace3() affiche le temps qui s’est écoulé entre Trace2 et Trace3.

    static Stopwatch sw = Stopwatch.StartNew();
     
    Action Trace1 = () => Trace.WriteLine("**************\n1 >");
    Action Trace2 = () => { Trace.WriteLine("2 >>"); sw.Restart(); };
    Action Trace3 = () => { Trace.WriteLine(sw.Elapsed); Trace.WriteLine("3 <<"); };
    Action Trace4 = () => Trace.WriteLine("4 <");

    A l’exécution, j’obtiens le résultat suivant:

    **************
    1 >
    2 >>
    Fin Action1
    Fin Action2
    Fin Action3
    00:00:06.0063690
    3 <<
    4 <

     

    Les 3 actions se sont exécutées en 6 secondes :
    Nos 3 actions ont démarrées chacune après la fin de la précédente (séquentiel) et rien d’autre ne s’est passé entre temps : la ligne de code qui suit (Trace3() ) ne s’est exécutée qu’après la fin de l’exécution des 3 tâches (synchrone).

    Le lot de tâches s’est donc exécuté de manière séquentielle et synchrone.


    Bon jusque là ce n’est pas très compliqué, les appels séquentiels c’est le genre de code que l’on écrit habituellement. Si je regarde mes 3 tâches d’un peu plus près, je constate qu’il n’y pas de couplage entre elles. Peu importe l’ordre dans lequel elles s’exécutent, le résultat fonctionnel sera le même. Profitons donc de cette caractéristique pour optimiser l’exécution de ces actions et utilisons la TPL pour les lancer en parallèle.

    La TPL (Task Parallel Library) fournit un framework de plus haut niveau que les threads : elle se base sur un mécanisme de tâches (Task) que l’on peut paralléliser, attendre, chainer de manière conditionnelle, etc.
    Nous verrons cela au fil des exemples, mais commençons par une approche simple en profitant au maximum de cette couche d’abstraction de niveau supérieur.

    Comment exécuter nos tâches en parallèle, pour répartir la charge de travail sur plusieurs processeurs ?

    Pour notre exemple, la TPL nous permet de réaliser cela de différentes manières:

     

    2. Appel des actions en parallèle avec Parallel.Invoke()

    Parallel.Invoke permet d’exécuter une collection d’actions en parallèle et rend la main une fois toutes les actions terminées, fournissant ainsi un mécanisme de synchronisation.
    Nous plaçons donc nos actions dans un tableau.

    private void Button_ParallelInvoke(object sender, RoutedEventArgs e)
    {
        Trace1();
     
        ParalleleInvoke();
     
        Trace4();
    }
     
    void ParalleleInvoke()
    {
        Action[] actions = { action1, action2, action3 };
     
        Trace2();
     
        // Appel des actions en parallèle par Invoke
        Parallel.Invoke(actions);
     
        Trace3();
    }

    A l’exécution on obtient:

    **************
    1 >
    2 >>
    Fin Action1
    Fin Action2
    Fin Action3
    00:00:03.0036246
    3 <<
    4 <

     

    Remarquez que l’exécution du lot d’actions n’a pris que 3 secondes (=la durée de l’action la plus longue) et que l’exécution de Parallel.Invoke est synchrone : la ligne de code Trace3() ne s’est exécutée qu’après la fin des 3 actions.
    C’est la TPL qui se charge du travail de synchronisation à notre place.
    Le lot d’actions s’est exécuté de manière parallèle et synchrone au sens où Parallel.Invoke est synchronisé avec la fin de l’exécution des actions.

     

    De la même manière, on peut utiliser d’autres mécanismes de la TPL pour obtenir le même résultat pour notre exemple comme par-exemple Parallel.ForEach qui permet de paralléliser le code appliqué aux éléments d’une collection.
    Vous pouvez évidemment profiter de Parallel.ForEach très facilement dans vos projets, du moment qu’il n’y a pas de ressource critique non protégée dans le code du foreach.


     

    3. Appel des actions en parallèle avec Parallel.ForEach

    private void Button_ParallelForeach(object sender, RoutedEventArgs e)
    {
    Trace1();
    ParallelForeach();
    Trace4();
    }

    void ParallelForeach()
    {
    Action[] actions = { action1, action2, action3 };

    Trace2();

    // Appel des actions en parallèle
    Parallel.ForEach(actions, a => a());

    Trace3();
    }

    A l’exécution on obtient:
    **************
    1 >
    2 >>
    Fin Action1
    Fin Action2
    Fin Action3
    00:00:03.0590707
    3 <<
    4 <

    Le lot d’actions s’est exécuté de manière parallèle et synchrone comme dans l’exemple précédent avec Parallel.Invoke.

     

    PLinq nous fournit une implémentation parallèle de Linq To Objects. Nous modifions quelque peu notre exemple pour partir d’une collection d’entiers qui représente les délais d’attente pour chaque action.



    4. Appel des actions en parallèle avec PLinq

    private void Button_PLinq(object sender, RoutedEventArgs e)
    {
        Trace1();
        ParallelLinq();
        Trace4();
    }
     
    // PLinq
    void ParallelLinq()
    {
        Trace2();
     
        int[] delays = { 1000, 2000, 3000 };
        Action<int> action =
            (ms) => { Thread.Sleep(ms); Trace.WriteLine("\tFin Action"); };
     
        delays.AsParallel().ForAll(ms => action(ms));
     
        Trace3();
    }
     
    A l’exécution on obtient:
     
    **************
    1 >
    2 >>
    Fin Action
    Fin Action
    Fin Action
    00:00:03.0056306
    3 <<
    4 <

    Le lot d’actions s’est exécuté encore une fois de manière parallèle et synchrone.
     

     

    Imaginons maintenant que nous souhaitions conserver le parallélisme, mais ne souhaitions plus exécuter le lot de tâches de manière synchrone, c’est à dire continuer l’exécution du code qui suit l’appel aux actions sans attendre qu’elles se terminent.
    Pour cela, nous allons travailler directement sur les objets Task fournis par la TPL.



    5. Appel des actions en parallèle et asynchrone avec l’objet Task

    On utilise la Task factory qui à partir d’une action, instancie une Task et la démarre.

    void Button_TaskPara(object sender, RoutedEventArgs e)
    {
        Trace1();
        TaskPara();
        Trace4();
    }
     
    void TaskPara()
    {
        Trace2();
     
        // Lance les 3 tâches en // sans attendre leur fin
        Task.Factory.StartNew(action1);
        Task.Factory.StartNew(action2);
        Task.Factory.StartNew(action3);
     
        Trace3();
    }

    A l’exécution on obtient:


    **************
    1 >
    2 >>
    00:00:00.0019663
    3 <<
    4 <
    Fin Action1
    Fin Action2
    Fin Action3
     

    Remarquez que cette fois-ci, les traces s’affichent suivant la séquence 1, 2, 3, 4 et les traces des actions n’arrivent que plus tard. StartNew() démarre les tâches et passe à la suite, sans qu’il y ait de mécanisme de synchronisation. Le lot de tâches s’exécute de manière parallèle et asynchrone .


     

    C’est intéressant de lancer les actions de manière parallèle et asynchrone : on libère le thread du dispatcher qui permet de conserver une interface réactive.
    Malgré tout, je suis un peu embêtée car maintenant je ne sais plus combien de temps prend l’exécution de mes 3 actions.
    Comment faire pour conserver l’asynchronisme, le parallélisme ainsi que l’appel à trace3 (qui affiche le temps qui s’est écoulé entre le début et la fin de mes actions) qui devrait plutôt se faire après l’exécution de mes 3 actions ?

    C’est là qu’interviennent les méthodes de continuation des objets Tasks et c’est aussi là que la TPL devient encore plus intéressante.



    6. Appel des actions en parallèle et asynchrone avec continuation

    La méthode  ContinueWhenAll() permet de chainer une tâche de continuation qui s’exécutera lorsque toutes les actions seront terminées. Ces tâches s’exécuteront en parallèle et de manière asynchrone: ContinueWhenAll rend la main immédiatement.

    void Button_TaskPara(object sender, RoutedEventArgs e)
    {
        Trace1();
        TaskParaContinue();
        Trace4();
    }
     
    void TaskParaContinue()
    {
        Action[] actions = { action1, action2, action3 };
     
        Trace2();
        Task.Factory.ContinueWhenAll(
                actions.Select(a => Task.Factory.StartNew(a)).ToArray(),
                (at) => Trace3());
    }

    On obtient le résultat suivant à l’exécution:

    **************
    1 >
    2 >>
    4 <
    Fin Action1
    Fin Action2
    Fin Action3
    00:00:03.0081844
    3 <<

    Cette fois, on exécute bien le lot de tâches de manière asynchrone : Trace4 s’affiche avant la fin des actions. Pour autant, on a pu chainer nos actions à une tâche de continuation qui contient Trace3() et qui nous permet de vérifier que nos tâches s’exécutent bien en parallèle : elles se terminent au bout de 3 secondes.
     
     

     

    Bon ben c’est bien sympa tout ça…mais du coup, à quoi peut bien nous servir le pattern async/await ?
    Pour rappel, la pattern async/await est disponible pour le moment avec la CTP async pour Visual Studio.


    7. Appel des actions de manière séquentielle et asynchrone avec async/await

    Installez la CTP Async pour Visual Studio 2010 et référencez l’assembly AsyncCtpLibrary pour pouvoir utiliser async et await.

    (Attention, la CTP n'est pas compatible avec le SP1 bêta de Visual Studio 2010 !)

    Une méthode qui contient un appel asynchrone doit toujours être préfixée par le mot-clé async.
    Le mot clé await me permet de lancer une tâche de manière asynchrone et de ressortir immédiatement du contexte de ma méthode préfixée par async. On ne reviendra exécuter la ligne de code qui suit l’appel à la tâche uniquement lorsque celle-ci sera terminée. Pour nos 3 actions, cela revient à les exécuter de manière séquentielle, mais en asynchrone Confus.

    Pour ceux qui connaissent yield, await est aux tâches ce que yield est aux collections Premier de la classe.

    En fait, c’est beaucoup plus facile à comprendre en regardant le résultat de l’exécution du code suivant:

    private void Button_Async(object sender, RoutedEventArgs e)
    {
        Trace1();
        AsyncProcSeq();
        Trace4();
    }
     
    async void AsyncProcSeq()
    {
        Trace2();
     
        await Task.Factory.StartNew(action1);
        await Task.Factory.StartNew(action2);
        await Task.Factory.StartNew(action3);
     
        Trace3();
    }

    On obtient le résultat suivant à l’exécution:

    **************
    1 >
    2 >>
    4 <
        Fin Action1
        Fin Action2
        Fin Action3
    00:00:06.0407339
    3 <<


    Le premier appel asynchroneawait Task.Factory.StartNew(action1 ) ) rend immédiatement la main en ressortant du contexte de la méthode AsyncProcSeq, et en exécutant la suite des lignes de code de la méthode appelante, à savoir Trace4() (un peu comme avec yield).
    A la fin de l’exécution de action1, l’exécution asynchrone de l’action2 sera déclenchée, et il en sera de même ensuite pour action3 une fois que l’exécution de action2 sera terminée.

    Trace3() sera appelé à la fin de l’exécution des 3 actions, tout comme avec le code de continuation de tout à l’heure. Attention, par contre les 3 actions ne s’exécutent pas en parallèle ici, mais bien de manière séquentielle : elles ont mis 6 secondes à s’exécuter.

    L’appel asynchrone async/await est donc très différent du démarrage d’une tâche parallèle dont on attend la fin !
    Le chemin d’exécution sera en effet tout autre.

    Si l’on souhaite exécuter ces actions en parallèle tout en conservant cette simplicité d’écriture, il suffit de coupler la TPL à async/await de la manière suivante:



    8. Appel des actions en parallèle et asynchrone avec async/await et la TPL

    private void Button_AsyncPara(object sender, RoutedEventArgs e)
    {
        Trace1();
        AsyncProcPara();
        Trace4();
    }
     
    async void AsyncProcPara()
    {
        Action[] actions = { action1, action2, action3 };
     
        Trace2();
     
        await Task.Factory.StartNew(() => Parallel.Invoke(actions));
     
        Trace3();
    }

    A l’exécution, on obtient le résultat suivant:

    **************
    1 >
    2 >>
    4 <
    Fin Action1
    Fin Action2
    Fin Action3
    00:00:03.0168947
    3 <<


    Les actions se sont exécutées en parallèle (les actions ne prennent que 3 secondes pour s’exécuter) et de manière asynchrone (on rend la main à l’appelant) tout en assurant l’équivalent d’une continuation pour toutes les lignes de code qui apparaissent à la suite de l’appel asynchrone c’est à dire Trace3().


     

    Voilà, la boucle est bouclée !

    Il est à noter que le pattern async/await sera plutôt utilisé dans les cas où l’on a besoin d’effectuer une séquence d’appels asynchrones de manière séquentielle (c’est à dire comme dans démo 7.) comme par-exemple des appels à des web services en cascade.
    En effet, il simplifie grandement l’écriture d’un code de continuation qui aurait été nécessaire avec l’utilisation de la TPL (cf démo no 6).

    Pour aller plus loin:

    A vos claviers !

  • Stéphanie Hertrich

    TechDays 2011 : Mes sessions en détail

    • 0 Comments

    thumb_signemail_TD11

    Nous voilà en pleine préparation des Techdays 2011

    En plus de participer à la plénière, j’aurai le plaisir d’animer 4 sessions avec des co-speakers de choc et sur des sujets plutôt variés.

    Ce qu'il ne fallait pas manquer depuis .Net 2 (LAN303)

    Animé par Stephanie Hertrich , Florent Santin
    Thème(s) :
    Architecture et Développement
    Audience : Développeurs
    Niveau :
    Intermédiaire (200)
    Le mardi 8 février 2011, 11:00 - 12:00.  

    Vous avez la tête dans le guidon et vous n'avez pas le temps de faire de veille techno ? Vous n'avez pas suivi les évolutions fonctionnelles des outils et du Framework (d'ailleurs vous n'en avez pas besoin) ? Prenez une heure pour découvrir une sélection de pépites qu'il ne fallait pas rater depuis .Net 2 (LINQ, MEF, Entity Framework, Async, SketchFlow…) Découvrez en quoi cela peut vous aider dans le développement de vos projets au quotidien. Un vrai lifting qui vous donnera le sourire...et l'envie de coder :)

    Attention, cette session ne va pas vous présenter WPF, Silverlight, WCF, etc…
    Le but ici est de montrer un ensemble d’outils du langage, du framework, et des IDE à côté desquels vous êtes peut-être passés. C’est une session à laquelle je tiens beaucoup et qui est destinée aux développeurs qui n’ont pas changé leur manière de coder depuis plusieurs années et/ou qui travaillent sur le même projet depuis longtemps. On verra que même sans changer de technologie on peut booster son développement et sa productivité. Le choix des sujets présentés sera fait de manière totalement arbitraire Sourire  par Florent et moi-même.


    Best Practices de développement pour Sharepoint Online (SHS201)

    Animé par Stephanie Hertrich , Philippe Sentenac
    Thème(s) : Efficacité individuelle et collective
    Audience : Administrateurs Développeurs Information Workers
    Niveau : Intermédiaire (200)
    Le mardi 8 février 2011, 14:30 - 15:30.  

    Avec l'arrivée de Microsoft SharePoint 2010 Online, de nouvelles possibilités d'extension de la plateforme SharePoint Online sont disponibles pour les développeurs. Mais développe-t-on de la manière manière sur Sharepoint OnLine que sur Sharepoint On Premises ? Comment contourner les restrictions du mode mutualisé dans nos développement ? Nous aborderons le modèle d'exécution sandbox ainsi que les nouveaux modèles d'accès aux données côté client : le Client Object Model et l'API Rest

    Produit(s) concerné(s)

      Dans cette session, nous aborderons les 3 niveaux de personnalisation : In-Browser, Sharepoint Designer et développement avec Visual Studio.
      Beaucoup de démos…

       

      Choisir une technologie d'accès aux données distante (ARC303)

      Animé par Stephanie Hertrich , David Rousset
      Thème(s) :
      Architecture et Développement
      Audience : Architectes Développeurs
      Niveau :
      Confirmé (300)
      Le mercredi 9 février 2011, 11:00 - 12:00.  

      Comment exposer et consommer vos données sous la forme de services ? OData, WCF, RIA Services,...SOAP ou REST : Ca donne le vertige ! Cette session vous montrera comment faire le bon choix en fonction de votre contexte et de votre environnement (Serveur, Cloud, Smartphone, HTML5, ...).

      Les sujets abordés dans cette session ont été très bien développés dans les derniers articles de David
      A côté de ça, je suis ravie de co-animer cette session avec David Sourire


      Women in IT : les bénéfices de la diversité (WIT101)

      Animé par Stephanie Hertrich , Stéphanie  Pelaprat, Véronique Méry, Cecille Boisson
      Thème(s) :
      Enjeux
      Audience : Administrateurs Architectes DBA Décideurs Designers Développeurs Education Information Workers
      Niveau :
      Découverte (100)
      Le mercredi 9 février 2011, 14:30 - 15:30.  

      La tendance est forte depuis quelques années : les femmes désertent les métiers de l'informatique et sont moins nombreuses dans les écoles d'ingénieurs. L'image stéréotypée de l'informaticien nuit-il à l'attractivité du secteur ? A l'occasion des Microsoft TechDays 2011, lors d'une session Women in IT, nous réunirons des femmes ayant brillamment réussies dans les nouvelles technologies. Elles vous feront partager leur expérience, leur évolution de carrière ou leur démarche entrepreneuriale. Elles prouveront que, oui, la femme à sa place dans ce secteur, non l'informatique n'est pas qu'un métier d'homme : Marie-Estelle Carrasco : Marketing Intelligence – Division Grand Public - Microsoft, animera cette session. Intervenants : * Stéphanie Pelaprat : CEO de Restopolitan * Céline Lazhorthes : CEO Leetchi.com * Séverine Roche : Administrateur Système Windows - Banque de France * Véronique Mery : Responsable d'Equipe d'Ingénieurs support Grands Comptes - Microsoft * Stéphanie Hertrich : Relation Technique Développeur - Microsoft

      Produit(s) concerné(s)

      • Pas de produit associé

      Une session qui est le sujet de mon billet précédent

      N’hésitez pas à me contacter si vous avez des questions concernant ces sessions et leur contenu.

      A très bientôt ! Verre à cocktail

    • Stéphanie Hertrich

      Techdays 2011 : Les femmes dans l’IT alias “Girl Power”

      • 2 Comments

      Comme tous les ans, je participerai à la session Femmes dans l’IT des Techdays…mais cette fois en tant que speaker ! Vous découvrirez pourquoi en regardant ce petit teaser

      Une session sympathique et décontractée pour parler de la diversité dans les métiers de l’informatique…qui tourne parfois (et pour mon plus grand plaisir) en “comment mieux s’en sortir au quotidien avec nos hommes (à la maison, au boulot, au match de basket du petit dernier, à l’école pour les réunions parents/profs, dans le métro/train/avion, et au dodo Sourire… )” ou plus généralement “mais comment font les autres pour tout concilier ?”.

      Euh ben en fait, souvent comme vous…c’est à dire comme elles peuvent !

      Le thème de cette année sera plutôt axé sur la réussite des femmes dans les métier de l’informatique, et quels bénéfices on peut tirer de cette diversité…

      J’en profiterai pour vous faire part de mon expérience et de mon quotidien auprès de mes chers petits collègues masculins chez Microsoft.

      [Petit message perso : les gars, je vous conseille vivement d’être adorables et irréprochables d’ici la session parce que…ça va balancer !!!! ]


      Ninja Les garçons : sont évidemment aussi les bienvenus…si vous cherchez des filles, c’est ici que vous les trouverez Sourire

      Princesse Les filles : si vous cherchez une petite coupe de champagne pour adoucir vos Techdays, c’est ici aussi !


      Venez nombreux et nombreuses !

      Les femmes dans l’IT alias “Girl Power” c’est le mercredi 9 février (J2) à 14h30 Verre à cocktail

      Women in IT : les bénéfices de la diversité  (WIT101)

    Page 1 of 1 (3 items)