Apprivoiser les fonctions asynchrones – Solutionnaire

Quelques solutions aux exercices de apprivoiser-async.html

EX00

Le programme suivant lit une URL de manière synchrone et dépose le résultat dans un fichier :

using System.Threading.Tasks;
using System.IO;
using System.Net.Http;
public class Program
{
   public static void Main()
   {
      using (var client = new HttpClient())
      {
         var s = client.GetStringAsync("http://h-deb.clg.qc.ca").Result; // URL prise au hasard
         using (var sw = new StreamWriter("h-deb-racine.txt"))
            sw.Write(s);
      }
   }
}

Transformez ce programme pour qu'il consomme une série d'URL de manière asynchrone et place le texte consommé de chacune dans un fichier distinct. Pour ce faire, écrivez une fonction asynchrone LireUrl() qui lira une URL et retournera un Task<string>, puis appelez cette fonction pour chaque URL à consommer.

Possible solution :

using System.Threading.Tasks;
using System.IO;
using System.Net.Http;
using System.Collections.Generic;
public class Program
{
   static async Task<string> LireUrl(HttpClient client, string url) =>
      await Task.Run(() => client.GetStringAsync(url));
   static async Task ÉcrireFichier(string nom, Task<string> texte)
   {
      await Task.Run(() =>
      {
         using (var sw = new StreamWriter(nom))
            sw.Write(texte.Result);
      });
   }
   public static void Main()
   {
      using (var client = new HttpClient())
      {
         (string, string)[] urls =
         {
            ("http://h-deb.clg.qc.ca", "racine"),
            ("http://h-deb.clg.qc.ca/CLG/Cours/420KBB", "KBB"),
            ("http://h-deb.clg.qc.ca/CLG/Cours/420201", "201")
         };
         var tâches = new List<Task>();
         foreach (var (url, fich) in urls)
            tâches.Add(ÉcrireFichier(fich + ".txt", LireUrl(client, url)));
         Task.WaitAll(tâches.ToArray());
      }
   }
}

Le gain ici n'est pas énorme; toutefois, si nous devions appliquer des opérations sur le texte de l'URL, nous pourrions gagner plus (voir EX01 pour une ébauche d'un exemple plus fécond).

EX01

Examinez le pipeline proposé à EX02 de exercice-apprivoiser-multiprog.html. Écrivez un programme équivalent, mais où plutôt que d'avoir un fil d'exécution par tâche, vous avez une fonction asychrone par tâche.

Possible solution (synchrone) :

   static (string nom, string s) LireFichier(string nom)
   {
      using (var sr = new StreamReader(nom))
         return (nom, sr.ReadToEnd());
   }
   static (string nom, string s) TransformerMajuscules(string nom, string s) =>
      (nom, s.ToUpper());
   static void ÉcrireFichier(string nom, string s)
   {
      using (var sw = new StreamWriter(nom + ".nouveau"))
         sw.Write(s);
   }
   public static void Main()
   {
      string[] noms = { "../../../Program.cs", "../../../a.txt", "../../../b.txt" };
      foreach(string nom in noms)
      {
         var (n0, s0) = LireFichier(nom);
         var (n1, s1) = TransformerMajuscules(n0, s0);
         ÉcrireFichier(n1, s1);
      }
   }

Possible solution (asynchrone) :

   static (string nom, string s) LireFichier(string nom)
   {
      using (var sr = new StreamReader(nom))
         return (nom, sr.ReadToEnd());
   }
   static async Task<(string nom, string s)>
      LireFichierAsync(string nom) =>
         await Task.Run(() => LireFichier(nom));
   static (string nom, string s)
      TransformerMajuscules(string nom, string s) =>
         (nom, s.ToUpper());
   static async Task<(string nom, string s)>
      TransformerMajusculesAsync(string nom, string s) =>
         await Task.Run(() => TransformerMajuscules(nom, s));
   static void ÉcrireFichier(string nom, string s)
   {
      using (var sw = new StreamWriter(nom + ".nouveau"))
         sw.Write(s);
   }
   static async Task ÉcrireFichierAsync(string nom, string s) =>
      await Task.Run(() => ÉcrireFichier(nom, s));
   static async Task ExécuterPipeline(string nom)
   {
      var (n0, s0) = await LireFichierAsync(nom);
      var (n1, s1) = await TransformerMajusculesAsync(n0, s0);
      await ÉcrireFichierAsync(n1, s1);
   }
   public static void Main()
   {
      string[] noms = { "../../../Program.cs", "../../../a.txt", "../../../b.txt" };
      var tâches = new List<Task>();
      foreach(string nom in noms)
         tâches.Add(ExécuterPipeline(nom));
      foreach (var tâche in tâches)
         tâche.Wait();
   }

Notez ce que j'ai fait : j'ai conservé les versions synchrones des fonctions, et j'ai fait des versions asynchrones qui délèguent aux fonctions synchrones en les logeant dans une tâche lanceé par Task.Run. Ceci permet un enchaînement d'opérations asynchrones sans devoir tout réécrire.

Notez aussi les appels à Wait sur chaque tâche à la fin du programme; nous souhaitons que les entrées / sorties se complètent avant la fin de l'exécution du programe.


Valid XHTML 1.0 Transitional

CSS Valide !