Async/Await: comprendre comment ca marche
Tout le monde est unanime pour dire que la programmation multi-thread et asynchrone est en train de devenir un sujet incontournable.
Beaucoup de choses sont arrivées avec le framework 4 pour le code parallèle (TPL, PLinq,…) et bientôt, on va avoir le droit à l’api async/await pour la partie Async.
Au temps vous le dire de suite, j’ai pas tout de suite compris les subtilités apportés par Async et j’étais donc en mode (idiot) “Async? m’en fous, ca sert a rien, avec TPL on peut déjà tout faire” …MAIS, je me suis dit que si Microsoft l’avait sortie et intégré dans le framework, c’est que ca mérite quelques heures à faire joujoux avec.
après avoir installé la CTP d’async, ma première bonne surprise est la qualité et la richesse de la documentation (a quand la même chose sur les API TFS
?):

Pour le coup, il y a vraiment tout pour bien commencer.
Maintenant, entrons dans le vif du sujet: le code!
on va prendre un exemple simple; une classe qui contient deux méthodes qui simulent des traitements court (1s) et le second long (2s):
1: public class AsyncExemple
2: {
3: private string TraitementLong()
4: {
5: System.Threading.Thread.Sleep(2000);
6: return "traitement long fini";
7: }
8:
9: private string TraitementCourt()
10: {
11: System.Threading.Thread.Sleep(1000);
12: return "traitement Court fini";
13: }
14: }
Avec les Tasks, si je souhaite effectuer les deux traitements en parallèle, il me suffit de faire ca:
1: public void ParallelTaskRun()
2: {
3: Task.Factory.StartNew(() =>{Console.WriteLine(TraitementLong());});
4: Task.Factory.StartNew(() =>{Console.WriteLine(TraitementCourt());});
5: }
et si j’exécute le code suivant:
1: Console.WriteLine("Début du traitement");
2: AsyncExemple e = new AsyncExemple();
3: e.ParallelTaskRun();
4: Console.WriteLine("Fin du traitement");
5: Console.ReadLine();
On a le résultat (attendu) suivant:

On a lancé nos deux traitements dans des Threads séparés, du coup on a le message de fin de traitement avant d’avoir ceux des traitements long et court ET le traitement court se fini avant le long.
Maintenant la même chose avec async, prenons le code suivant (on simule en plus un chargement):
1: public async void Run()
2: {
3:
4: System.Threading.Thread.Sleep(5000);
5: Console.WriteLine("...fin du chargement");
6: string text1 = await TaskEx.Run(() => { return TraitementLong(); });
7: Console.WriteLine(text1);
8: string text2 = await TaskEx.Run(() => { return TraitementCourt(); });
9: Console.WriteLine(text2);
10: }
On obtient le résultat suivant:
Là, ca devient intéressant. On remarque que :
- le chargement s’effectue de manière séquentiel malgré le fait qu’il soit dans la méthode marquée async.
- les traitements marqués avec await s’exécutent bien de manière asynchrone par rapport à la méthode appelante (le Thread sur lequel s’exécute la méthode qui appelle la méthode async) MAIS de manière séquentiel.
Ce qui est asynchrone est donc ce qui est préfixé avec le mot clé await et est relatif au contexte de la méthode qui appelle la méthode async.
Prenons un autre exemple pour clarifier cette dernière phrase:
1: public void Run()
2: {
3: TraitementLongAsync();
4: TraitementCourtAsync();
5: }
6:
7: public async void TraitementLongAsync()
8: {
9: string text = await TaskEx.Run(() => { return TraitementLong(); });
10: Console.WriteLine(text);
11: }
12:
13: private async void TraitementCourtAsync()
14: {
15: string text = await TaskEx.Run(() => { return TraitementCourt(); });
16: Console.WriteLine(text);
17: }
On a encapsulé les tratiements dans des méthodes asynchrones distinctes et on les lance depuis une méthode Run…si vous avez bien compris ce que j’ai dit plus haut, vous devriez savoir quel va être le résultat qui est …

…le même qu’avec les Tasks ! logique 
les méthodes se lancent de manière asynchrone par rapport au contexte de la méthode appelante Run.
On a donc:
- la méthode Run appelle la méthode asynchrone chargée de lancer le traitement long.
- Celle-ci lance le traitement long en asynchrone et rend la main à la méthode Run
- qui lance la méthode chargée de lancer le traitement long
- …vous avez compris la suite
On voit bien ici qu’il y a quelques subtilité à appréhender. Ce qu’il est important de comprendre également c’est que sur une console ca n’a pas beaucoup de sens d’utiliser async, par contre, lorsque vous avez à faire des applications graphiques, cela devient extrêmement puissant. Si on prend l’exemple du Thread UI abordé dans mon post précédent. avec async il suffit juste de faire ca pour ne plus avoir à se poser de questions:
1: private async void button1_Click(object sender, RoutedEventArgs e)
2: {
3: textBox1.Text = await TaskEx.Run(() =>
4: {
5: System.Threading.Thread.Sleep(2000);
6: return "toto";
7: });
8: }
le code est tout de même beaucoup plus élégant et on a plus besoin de se demander comment on retourne sur le Thread UI….ou comme le dirait Squizz; “la classe! totaaaale”.
RQ: on a la possibilité de faire ca directement sur le code du bouton car il a le bon gout de ne rien renvoyer (void).
Pour finir et voir si vous avez bien compris, que ce passe t’il si j’exécute le code suivant:
1: private async void button1_Click(object sender, RoutedEventArgs e)
2: {
3: textBox1.Text = await TaskEx.Run(() =>
4: {
5: System.Threading.Thread.Sleep(2000);
6: return "toto";
7: });
8:
9: System.Threading.Thread.Sleep(5000);
10:
11: textBox1.Text = await TaskEx.Run(() =>
12: {
13: System.Threading.Thread.Sleep(2000);
14: return "tata";
15: });
16: }
sans l’exécuter bien sûr 
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :