Fév 19 2013

C# / Rendre une méthode utilisable avec async et await ?

Voilà fort longtemps que je n’ai pas écrit d’article… Pourtant Dieu sait que j’en ai encore plein à faire que ce soit pour finir mon tuto ASP.NET mais le sujet dont je vais vous parler me paraissait important d’être abordé.

Le développement asynchrone, kézako ?

Aujourd’hui, le développement asynchrone est devenu incontournable.

Il est indispensable quand on développe en Javascript et il devient de plus en plus courant quand on développe sur les plateformes Microsoft, notamment Windows 8 et Windows Phone mais aussi ASP.NET et WPF.

Si cette notion d’asynchronisme ne vous parle pas, c’est que vous n’avez développé jusqu’à présent qu’en synchrone : c’est à dire que les instructions de votre code s’éxécutent les unes après les autres.

Tant que la ligne 1 n’a pas fini son exécution, la ligne 2 ne se lance pas. Vous suivez ?

Ce paradigme était vrai lorsque l’on développait des applications de gestion « classiques » pour des utilisateurs PC. Quand l’utilisateur enregistre une donnée en base, ou que cette donnée doit être tramsmise à un webservice, l’interface se  « freeze » quelques secondes le temps que l’appel à la ressource extérieure soit exécuté. On bloque alors le thread UI.

Aujourd’hui qu’en est-il ? Nous disposons de téléphones dernier cri et de tablettes tactiles. Imaginez un seul instant que le tactile ne réponde plus au motif que votre application attend la réponse d’un serveur. Ce n’est pas possible. L’interface doit être toujours réactive, sinon votre utilisateur pensera que l’application a buggé.

Pour cela, il faut changer notre façon de développer. Il ne faut plus bloquer le thread UI. C’est là ou le développement asynchrone rentre en jeu.

Plutôt que de « freezer » l’interface, nous allons montrer un indicateur d’attente à notre utilisateur. De cette façon, nous pourrons aussi lui proposer d’autres fonctionnalités en attendant et l’informer dès que l’action est terminée.

Exemple : J’envoi une photo de profil Facebook. La photo s’envoi mais je peux toujours continuer à consulter le flux d’actualité.

L’ancienne méthode :

Dans les versions de .NET <= 4.0, les appels asynchrones étaients faits comme ceci.

On s’abonne à l’évènement Completed où l’on définit les actions à faire une fois le traitement terminé, puis on lance la méthode Async().

public void LoadBooks()
{
   var service = new BookService();
   service.GetBooksCompleted += OnGetBooksCompleted;
   service.GetBooksAsync("Georges R. R. Martin");
}

Et voici l’implémentation de la méthode Completed :

private void OnGetBooksCompleted(object sender, GetDataCompletedEventArgs e)
{
   this.books = e.Result;
}

On disposait aussi du ThreadPool, des méthodes Begin/End etc. Mais cela pouvait vite devenir compliqué car il nous fallait à minima 2 méthodes pour simplement récupérer une liste de données.

Bien sûr, on pouvait aussi simplifier le code ci-dessus en remplaçant la méthode par un delegate :

public void LoadBooks()
{
   var service = new BookService();

   service.GetBooksCompleted += (sender, e) =&gt;
   {
      this.books = e.Result;
   };

   service.GetBooksAsync("Georges R. R. Martin");
}

La nouvelle méthode :

Depuis .NET 4.5 et C# 5.0, Microsoft a amélioré la librarie Task, et introduit deux nouveaux mots-clés: async et await, simplifiant ainsi les appels asynchrones.

public async Task LoadBooksAsync()
{
   var service = new BookService();
   this.books = await service.GetBooksAsync("Georges R. R. Martin");
}

Ces nouveaux mots-clés sont très pratiques, puis qu’ils nous permettent d’améliorer la lisibilité de notre code.

Important : Par convention, le nom des méthodes asynchrones doit se terminer par Async et avoir une Task comme type de retour. Seuls les event-handlers (click, mouseOver, touch etc.) sont autorisées à avoir un type de retour à void.

 

Mais parfois, le sort s’acharne et vous empêcher de les utiliser…

Mon problème :

J’ai voulu développer une application disponible à la fois sur Windows 8 et sur Windows Phone 8.

Selon MSDN, il est donc possible d’écrire du code commun aux 2 plateformes (soit du code portable).

Portable dans le sens où il peut être compatible à la fois pour Windows 8, Windows Phone, Silverlight et Xbox 360. Voici l’implémentation que Microsoft recommande au niveau des couches MVVM :

MVVM Portable Class Library

Chose intéressante : les ViewModels et Models peuvent être communs aux deux plateformes.

Je crée donc un nouveau projet de type Portable Class Library et j’indique qu’il sera compatible .NET 4.5, Windows 8 et Windows Phone 8. Pas besoin de plus pour le moment.

Portable Class Library: choix des technos compatibles

Sauf que…

Je fais un clic-droit sur mon nouveau projet et là… je vois que je ne peux pas ajouter de référence de service. Pas de webservice, pas de liaison à un service WCF possible…

Portable Class Library: Pas de référence de service

Finalement je découvre sur quelques forums qu’il faut aussi cocher Silverlight 5. C’est ce que je fais. Je retente et je constate bel et bien que je peux ajouter une référence de service.

Portable Class Library: ajouter une référence de service

Je croyais que c’était plié…

Deuxième mauvaise surprise :

J’ajoute donc mon webservice et je constate… Qu’il m’a généré du code avec « l’ancienne » méthode (avec l’événément Completed et la méthode Async) et non la nouvelle. Pas moyen d’utiliser les nouveaux mots-clés async et await.

Pourtant, j’ai bien sélectionné .NET 4.5 pour bénéficier de cette nouveauté. Sauf que j’ai aussi sélectionné Silverlight 5, qui lui s’appuie sur .NET… 4.0 !

Si je décoche Silverlight 5, je ne peux plus ajouter de références de services. Comment faire ?

La solution :

Vous avez besoin d’ajouter la référence Microsoft.Bcl.Async disponible via NuGet.

Ainsi, vous serez en mesure de créer des méthodes « awaitable », c’est à dire utilisables avec les mots-clés async et await.

public Task LoadBooksAsync(string author)
{
   var taskCompletionSource = new TaskCompletionSource();

   client.GetBooksCompleted += (sender, e) =&gt;
   {
      taskCompletionSource.TrySetResult(e.Result);
   };

   client.GetBooksAsync(author);

   return taskCompletionSource.Task;
}

Ainsi, vous serez en mesure de faire un appel comme ceci :

public async void MyMethodAsync()
{
   this.MyBooks = await LoadBooksAsync("George R. R. Martin");
}

Et le tour est joué 😉

Articles similaires:

Lien Permanent pour cet article : http://jbvigneron.fr/2013/02/19/csharp-methode-async-await/

Laisser un commentaire

Your email address will not be published.

css.php