Après deux jours d'étude intensive, consultation de nombreuses ressources en ligne et lecture de blogs spécialisés, j'ai finalement assimilées les bases fondamentales de l'utilisation combinée de async/await. Voici un résumé de mes découvertes :
- async/await représente avant tout un sucre syntaxique destiné à permettre aux développeurs de se concentrer sur la logique métier de leur code, sans se perdre dans les complexités des anciennes approches asynchrones comme Begin...End... ou les modèles IAsync...Async. Lors de mes premiers essais avec Tcp Listener, l'expérience fut véritablement cauchemardesque. Les opérations asynchrones devaient être placées dans des méthodes de rappel ou des événements distincts, et les opations asynchrones subséquentes générées par ces rappels nécessitaient à leur tour d'être placées dans d'autres méthodes de rappel, créant une complexité exponentielle, notamment lorsqu'il s'agissait d'implémenter des boucles.
- Les méthodes marquées avec async "ne créent pas activement de threads supplémentaires" - cette affirmation prête souvent à confusion. Des articles techniques détaillés comme "Les principes de la programmation asynchrone avec Async et Await" expliquent clairement que l'asynchronité implique nécessairement l'utilisation de multiples threads, particulièrement lorsqu'une opération est initiée depuis le thread d'interface utilisateur. La magie réside dans le fait que le framwork gère cette complexité de manière transparente, libérant le développeur des préoccupations relatives à la création, la destruction et la gestion des exceptions liées aux threads, tout en conservant une syntaxe élégante.
- await m'a initialement induit en erreur, faisant croire qu'une écriture comme var resultat = await FonctionAsynchrone(...) rendait automatiquement l'exécution non-bloquante pour le thread UI. C'est une erreur fondamentale que j'ai commise en ignorant un point crucial. Examinons d'abord le code problématique original :
1 private async Task<TimeSpan> Telecharger(Uri adresse, string nomFichier)
2 {
3 this.adresseSource = adresse;
4 this.fichierDestination = nomFichier;
5 DateTime debut = DateTime.Now;
6 //
7 HttpWebRequest requete;
8 HttpWebResponse reponse = null;
9 try
10 {
11 requete = (HttpWebRequest)WebRequest.Create(adresse);
12 reponse = (HttpWebResponse)requete.GetResponse();
13 using (Stream fluxHttp = reponse.GetResponseStream())
14 {
15 using (FileStream ecriture = new FileStream(nomFichier, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read))
16 {
17 byte[] tampon = new byte[8192];
18 int longeurLue = fluxHttp.Read(tampon, 0, tampon.Length);
19 //
20 while (longeurLue > 0)
21 {
22 ecriture.Write(tampon, 0, longeurLue);
23 longeurLue = fluxHttp.Read(tampon, 0, tampon.Length);
24 }
25 }
26 }
27 }
28 catch(Exception)
29 {
30 if (reponse != null) { reponse.Close(); }
31 }
32
33 return DateTime.Now - debut;
34 }
Ce code effectue le téléchargement d'une ressource vers un emplacement spécifique. Bien que la logique semble correcte et que la méthode soit marquée async avec un type de retour Task, mon appel initial était basé sur une fausse supposition.
1 private async void boutonTelecharger_Click(object sender, EventArgs e)
2 {
3 //Logique de validation omise pour la clarté
4 ......
5
6 TestApp.HttpDownloader.MoteurTelechargement moteur = new MoteurTelechargement();
7 var temps = await moteur.Telecharger(adresse, champFichier.Text);
8 MessageBox.Show(string.Format("Téléchargement terminé en {0} secondes", temps.Seconds.ToString()));
9 }
Appel problématique de la méthodeL'événement Click du bouton semblait correct, j'avais même soigneusement ajouté le mot-clé async (nécessaire pour pouvoir utiliser await). L'exécution fonctionnait effectivement - le fichier MP3 était bien téléchargé - mais mon interface utilisateur restait bloquée de manière synchrone jusqu'à la fin du processus. L'erreur se trouvait où ?
Après une pause réflexive et une nouvelle analyse des ressources disponibles, j'ai identifié le problème mentionné au point 3 : bien que la méthode HttpDownloadEngine.Download soit marquée async, elle ne contenait aucune instruction await réelle. En conséquence, malgré le mot-clé async, la méthode fonctionnait de manière synchrone. L'événement button_click appelait donc une méthode synchrone de manière bloquante.
La solution consiste à remplacer les opations bloquantes par leurs équivalents asynchrones en utilisant await Task.Async() aux points critiques où une exécution non-bloquante est nécessaire. Par exemple : longeurLue = await fluxReponse.ReadAsync(tampon, 0, tamponn.Length);
- Je tiens à remercier Microsoft, bien que je ne prétende pas être un expert. En s'inspirant et en créant .NET ainsi que l'environnement de développement Visual Studio d'une qualité exceptionnelle, l'entreprise a permis à des passionnés comme moi d'explorer les multiples facettes de l'inofrmatique. Ce qui rend cette discipline fascinante, c'est la capacité de créer et de partager.
Voici un exemple visuel du résultat obtenu après implémentation correcte des patterns asynchrones.