Comprendre Async (troisième étape)

Contexte

Dans l’étape précédente, j’avais promis de revenir sur Async, avec une application plus adaptée.

Pour ceux qui prennent en cours, ces quelques articles ont pour but de faire découvrir Async en montrant toutes les étapes de découverte, y compris et surtout les échecs. Ne prenez donc surtout pas le contenu pour argent comptant : il s’agit de manipulations et de bidouilles destinées à montrer la progression d’une personne pour appréhender les concepts d’Async, en espérant que ça serve à d’autres.

Nous allons donc repartir ce coup-ci d’une application avec GUI. Le problème en effet avec l’application de type Console est qu’elle est fondamentalement basée sur un thread de déroulement ordonné, unique, et donc synchrone. Nous avons besoin d’une application graphique pour montrer le réel intérêt d’Async.

L’application

Pour cela, une bête application simpliste de type WPF suffira. Un bouton pour lancer une commande, une TextBox pour afficher un résultat variable, et roule !

image

Si on travaille en mode synchrone, on utiliserait ce genre de code :

image

Evidemment, entre le moment où on clique sur le bouton et le moment où la GUI rend la main, impossible d’interagir avec elle, car tout fonctionne dans le même thread, qui s’occupe ici de l’affichage et de l’appel à un site web.

C’est précisément le scénario type pour lequel Async a été créé.

Solution à l’ancienne

Le problème étant posé, nous allons d’abord voir comment il était possible, avant Async, de le résoudre. L’idée était de mettre en place de l’asynchronisme, c’est-à-dire la séparation du traitement qui se déroule en parallèle de la gestion de la GUI. Pour cela, on utilisait un évènement de type Completed, et on associe un traitement qui s’occupe de récupérer le résultat du téléchargement pour le traiter :

image

Le problème est qu’on a tellement séparé qu’on se retrouve avec les deux parties du traitement qui se trouvent dans deux fonctions distinctes. Ceci pose problème, car on rompt la lecture du code, et ainsi qu’on ne manifeste pas correctement l’intention du développeur.

Solution de type Async

C’est pour cela qu’il fallait quelque chose de plus propre, comme Async. Async permet de concentrer l’asynchronisme dans une seule et même fonction, qui est syntaxiquement très proche du premier code que nous avions montré et qui était synchrone. Il “suffit” d’utiliser une fonction asynchrone qui renvoie une Task<string>, de rajouter le mot-clé await pour signifier que la TextBox sera remplie lorsque cette tâche sera terminée, et de mettre devant la fonction le mot-clé async, qui est nécessaire dans toute méthode employant l’asynchronisme :

image

Le gros avantage est bien sûr qu’on est très proche du code synchrone, et que la complexité est masquée. En arrière plan, c’est bien une vraie machine à état qui a été déclenchée. Si on jette un oeil avec ANTS Performance Profiler (attention, seule la toute dernière version Beta est capable de gérer de l’asynchronisme), on voit ce mécanisme sur la fonction marquée d’un tag Async (celle sélectionnée), ainsi que dans le code source retrouvé par décompilation (panel du bas), où on voit AyncStateMachine dans les attributs de la méthode :

image

Si on ouvre le graphique correspondant à cette méthode, le profileur de Red Gate nous montre bien le contenu déroulé par cette machine à état :

ANTS

On commence par un Start, puis un MoveNext qui nous amène sur la fonction de téléchargement au final.

Pourquoi cette machine à état ? Tout simplement parce que le mode d’Async est de ne pas utiliser un thread à part, mais bien de rester dans le thread principal en utilisant une machine à état qui se déroule dans ce même thread. Ceci permet d’éviter les problèmes de basculement de threads, traditionnellement associés au retour d’un thread de traitement dans le thread principal, celui de la GUI, pour l’affichage associé en retour.

Gérer l’attente

Après avoir abordé cet exemple simple (et sans avoir parlé de ce qui se passe dans DownloadStringTaskAsync, qui mériterait un autre article le jour où j’aurai suffisamment bien compris le contenu pour en parler), je fais juste un petit détour par autre chose qui m’a permis de comprendre un peu mieux Async. C’est tout bête, mais comme ces articles sont censés montrer tout le cheminement étape par étape de la compréhension d’Async (en tout cas, mon cheminement, en espérant qu’il conviendra à expliquer les concepts à d’autres), je le fais voir quand même.

Supposons que le clic sur un bouton appelle une fonction Attente définie comme ceci :

image

Logiquement, la GUI bloquera pendant les 3 secondes. Si on fait quelque chose qui pourrait paraître similaire dans Async, on a un comportement différent :

image

En effet, pendant les trois secondes, on peut manipuler l’interface. Ce qui se passe est qu’on est entré dans la machine à états, que le thread est repassé au traitement de la GUI, et qu’au bout de trois secondes, le thread est passé sur l’étape suivante de la machine à états, qui a consisté à redonner la main au code au niveau du await. Il se trouve qu’il ne fait rien, mais au moins pendant le délai, on n’a rien bloqué.

Conclusion

Voici pour un troisième article sur Async. Promis, il y en aura un autre quand j’aurai mieux compris ce qui se passe dans la fonction DownloadStringTaskAsync : est-ce qu’elle est elle-même déclarée async ? Comment fonctionne-t-elle ? Comment peut-on utiliser Async pour créer des fonctions avec le même type de fonctionnement ?

About JP Gouigoux

Jean-Philippe Gouigoux est Architecte Logiciel, MVP Connected Systems Developer. Il intervient régulièrement à l'Université de Bretagne Sud ainsi qu'à l'Agile Tour. Plus de détails sur la page "Curriculum Vitae" de ce blog.
This entry was posted in .NET and tagged . Bookmark the permalink.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha Captcha Reload