Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Les nouveaux chemins vers l’asynchronisme – I

Poster un commentaire

Je souhaite entamer une petite série sur la prise en charge de l’asynchronisme en C#. On va démarrer progressivement parce que le sujet est complexe, même si depuis l’apparition des Task, Microsoft a réussi un remarquable effort de simplification… et que dire du pattern async/await qui est carrément brillantissime.

 

Mais je pense déraisonnable d’essayer de comprendre async/await sans avoir une vision claire d’où on vient et pourquoi.

Nous allons commencer par définir le vocabulaire. Beaucoup de développeurs pense que l’asynchronisme a à voir avec les performances, ne savent pas définir la différence avec le multi-threading et je ne parle même pas du parallélisme!

On va donc essayer de démarrer sur des bases j’espère solides avant d’aborder les hauts rivages.

Eh, c’est pas mal cette formule. Je la recaserai dans un blog!

Les enjeux

Vous connaissez tous la loi de Moore qui à ce jour reste globalement vérifiée.

2014-10-02_16-48-33

Et pourtant côté fréquence on stagne depuis le début des années 2000:

2014-10-02_16-50-52

(source: http://csgillespie.wordpress.com/2011/01/25/cpu-and-gpu-trends-over-time/)

En tout cas le mythe du méga-hertz, plus personne n’y croit et on reste durablement scotché au 3.5 MHz. La principale raison de ce plafond, c’est l’échauffement des CPU. Alors comment fait t’on?

Si la loi de Moore continue de s’appliquer, c’est parce que l’on commence à mettre de plus en plus de processeurs sur une même puce. Il existe déjà, au moins en labo, des machines à plusieurs dizaines de CPU qui consomment moins de 100 W.

On peut aussi noter que Windows 2008 R2 peut gérer jusqu’à 256 CPU.

 

Quels sont les problèmes que résolvent les machines multi-cœur?

  • Asynchronisme
    C’est le fait de lancer une opération et de continuer sans attendre qu’elle termine. Elle se terminera plus tard à une date indéterminée.
    Asynchronisme s’oppose à synchronisme qui est le déroulement habituel des lignes de code.
  • Multi-threading
    Le fait d’exploiter plusieurs thread dans une application
  • Parallélisme
    Le fait d’exécuter plusieurs threads en même temps

Le parallélisme a besoin de plusieurs cœurs pour pouvoir être mis en place. Ce n’est pas tout, il faut également que votre algorithme puisse dégager des unités de travail capables d’être parallélisés. Parfois cela implique de restructurer le code ou de trouver un autre algorithme. Le parallélisme n’est pas toujours facile à mettre en place.

Le multi-threading et l’asynchronisme n’imposent rien de particulier en ce qui concerne le multi-cœur. Par exemple JavaScript qui est un langage mono-thread, peut parfaitement prendre en charge l’asynchronisme.

Ce qui est important de saisir est que ni le multi-threading, ni l’asynchronisme ne sont en soit des réponses à des problèmes de performance. L’asynchronisme, par exemple, a surtout pour objet de rendre responsif une interface. C’est un enjeu important pour les applications mobiles.

Le fait de créer de nombreux thread n’est pas non plus un gage de meilleure performance. Il faut encore qu’ils puissent s’exécuter en parallèle et donc il faut disposer de plusieurs cœurs.

D’une façon générale les techniques de mise en œuvre de l’asynchronisme et du multi-threading consomment des ressources et ont des temps de latence. Il est tout à fait envisageable d’observer qu’elles aggravent un problème de performance plus qu’elles ne le résolvent.

Je pense utile d’examiner un peu de code pour mieux insister sur ce point fondamental.

On se monte le projet WPF suivant:

<Grid>
    <Button Content="Synchrone" HorizontalAlignment="Left" Margin="32,75,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
    <Button Content="Asynchrone" HorizontalAlignment="Left" Margin="32,115,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click_1"/>
    <Label Name="label1" Content="Label" HorizontalAlignment="Left" Margin="32,33,0,0" VerticalAlignment="Top"/>
    <TextBox Name="label2" HorizontalAlignment="Left" Height="23" Margin="177,115,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="323"/>
</Grid>

Côté code ajoutons ceci:

// synchrone
private void Button_Click(object sender, RoutedEventArgs e)
{
    // on entre ici dans le UI thread
    // il n'y en a qu'un seul, donc tant que la tâche n'est pas terminée, l'interface utilisateur est figée
    label1.Content = "Chargement en cours...";
    label2.Text = "";

    // tâche longue
    // pendant son exécution, l'IHM est bloquée
    LongTask();

    label1.Content = "Terminé!";
}

// asynchrone
private void Button_Click_1(object sender, RoutedEventArgs e)
{
    label1.Content = "Chargement en cours...";
    label2.Text = "";

    // on extrait un thread du pool et on le lance en arrière plan
    ThreadPool.QueueUserWorkItem(_ => { LongTask(); });

    // on va afficher Terminé, mais en fait LongTask continue de tourner
    label1.Content = "Terminé!";
}

/// <summary>
/// Tâche particulièrement longue
/// </summary>
private void LongTask()
{
    int limite = 999999999;
    long total = 0;
    for (int i = 0; i < limite; i++)
    {
        total += i;
    }

    label2.Text = total.ToString();
}

Le rôle de LongTask est de perdre son temps pour avoir le temps d’observer. Observer quoi? Une exécution synchrone et une exécution asynchrone de LongTask.

2014-10-02_22-07-19

Le code sera détaillé dans un autre article, pour l’instant nous sommes surtout intéressé par les effets.

  • Cliquez sur Synchrone. Vous constatez que temps que LongTask n’a pas rendu la main l’interface ne réagit plus, elle est bloquée.
  • Cliquez sur Asynchrone. Cette fois tout est différent, l’interface continue à réagir et pourrait lancer d’autres commandes

Note: le plantage à la fin de la commande asynchrone est normal, nous le corrigerons plus tard.

Avec cet exemple je pense que l’importance de l’asynchronisme et la signification de ce terme devrait être particulièrement clair. J’aime bien les exemples.

Définir son environnement

Déjà comment faites vous pour savoir combien de cœurs vous disposez?

Le plus direct est bien sûr de lire la doc. Mais il y a plus rapide. Par exemple le moniteur de ressource confirme que j’ai 4 cœurs sur ma machine:

2014-10-02_17-10-34

On peut obtenir cette information en interrogeant Environment.ProcessorCount.

Attention, ce n’est pas parce que vous voyez 4 cœurs que l’on parle de 4 cœurs physiques. Il existe des CPU qui gèrent des cœurs logiques. Pour le dev, ça n’a pas une bien grande importance.

La suite

La suite est à voir dans les articles qui suivent où nous allons commencer à écrire du code. Le prochain article va donner une perspective historiques des différentes façons dont .Net gère l’asynchronisme et va doucement nous amener vers la notion de Task.

 

 

 

 

 

 

 

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