Tips & Tricks : Modèle TAP (Task Asynchronous Pattern, i.e async/await) avec la classe WebClient de Windows Phone 8

 

Le modèle TAP, est le modèle qui vous permet (pour faire simple) d’utiliser le couple async/await, dans votre code.

Par exemple avec Windows 8, si je veux lire un flux d’un fichier, je peux à l’aide de la classe HttpClient et de sa méthode GetStreamAsync utiliser ce modèle comme suit :

Code Snippet

  1. public async void LoadDataAsync()
  2.         {
  3.             var stream = await _clientHttp.GetStreamAsync(new Uri("..."));
  4.             
  5.         }

 

Ceci est rendu possible, car la méthode GetStreamAsync retourne un Task<Stream>

Néanmoins, avec la classe WebClient de Windows Phone 8, pour lire ce flux, outre le faite que la classe est différente par rapport à celle de Windows 8, la méthode se nomme OpenReadAsync, et ne retourne qu’un void. Pour obtenir le résultat, il faut s’abonner à un évènement (ce que nous évite de faire le modèle TAP justement et qui simplifie la lisibilité du code.)

Code Snippet

  1. public voidLoadRemoteFileAsync(string RemotePath)
  2.       {
  3.           WebClient _clientWeb = new WebClient();
  4.           _clientWeb.OpenReadAsync(new Uri(RemotePath));
  5.           _clientWeb.OpenReadCompleted += _clientWeb_OpenReadCompleted;
  6.       }
  7.  
  8.       void _clientWeb_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
  9.       {
  10.           //....Code omis pour plus de clart
  11.       }

 

Une des solutions consiste alors à utiliser une classe bien pratique en .NET la TaskCompletionSource.

Tout d’abord nous allons créer une méthode d’extension pour la classe WebClient, que je nommerai GetStreamAsync (pour utiliser la même nomenclature que la classe HttpClient et ceci afin de rendre plus lisible mon code, dans l’optique ou je vais créer une base de code commune entre Windows 8 et Windows Phone 8, mais ceci est une autre histoire.)

Pour ce faire, il faut déclarer une classe statique et une méthode statique dans cette classe prenant le paramètre this du type de la classe qu’on veut étendre, puis le paramètre de la méthode.

Code Snippet

  1. public static class WebClientEx
  2.     {
  3.        public static Task<Stream> GetStreamAsync(this WebClient webClient, String requestUri)

 

Ensuite dans le corps de la méthode nous utilisons la classe TaskCompletionSource, de la manière suivante :

Code Snippet

  1. public static class WebClientEx
  2. {
  3.    public static Task<Stream> GetStreamAsync(this WebClient webClient, String requestUri)
  4.     {
  5.         OpenReadCompletedEventHandler completedHandler = null;           
  6.         if (webClient == null)
  7.         {
  8.             throw new ArgumentNullException("webClient");
  9.         }
  10.         TaskCompletionSource<Stream> tcs = new TaskCompletionSource<Stream>(webClient);
  11.         completedHandler = delegate(object sender, OpenReadCompletedEventArgs e)
  12.             {
  13.                 if (e.Error != null)
  14.                 {
  15.                     tcs.TrySetException(e.Error);
  16.                 }
  17.                 else if (e.Cancelled)
  18.                 {
  19.                     tcs.TrySetCanceled();
  20.                 }
  21.                 else
  22.                 {
  23.                     tcs.TrySetResult(e.Result);
  24.                 }
  25.                 webClient.OpenReadCompleted -= completedHandler;
  26.             };
  27.  
  28.         webClient.OpenReadCompleted += completedHandler;
  29.         webClient.OpenReadAsync(new Uri(requestUri));
  30.         return tcs.Task;
  31.     }      
  32. }

 

 

  1. On crée un objet TaskComptetionSource qui retournera un Stream
  2. On crée un délégué qui a la même signature que l’évènement OpenReadCompleted que nous lui affecteront par la suite.
  3. Dans ce délégué, on utilise la méthode TrySetResult, qui retourne à l’appelant le Stream du fichier lorsque l’évènement OpenReadCompleted est déclenché.
  4. Pour attendre que l’évènement OpenReadCompleted soit déclenché, on retourne une tâche générée par le TCS.

 

 

Du coup maintenant nous pouvons utiliser la syntaxe async/await en utilisant notre méthode d’extension sur la classe WebClient.

 

 

Code Snippet

  1. public async voidLoadRemoteFileAsync(string RemotePath)
  2.       {
  3.           WebClient _clientWeb = new WebClient();
  4.           var stream = await _clientWeb.GetStreamAsync(RemotePath);
  5.       }

 

  

 

 

 

Eric