> Tech > Conseil n° 5 : Déterminez comment gérer les méthodes asynchrones

Conseil n° 5 : Déterminez comment gérer les méthodes asynchrones

Tech - Par Renaud ROSSET - Publié le 12 juillet 2012
email

Si, comme moi, vous aimez que votre code soit bien structuré en couches et bien organisé, vous allez finir par entrer en guerre contre les méthodes WCF asynchrones de Silverlight.

Conseil n° 5 : Déterminez comment gérer les méthodes asynchrones

Dans Silverlight, tout le trafic réseau doit être asynchrone. (Vous pensez sûrement que cela semble inoffensif pour le moment ?) Par conséquent, si vous avez une méthode de service WCF intitulée LoadPersonById et si vous essayez de l’appeler à partir de Silverlight, vous allez appeler LoadPersonByIdAsync et gérer les rappels asynchrones. Il existe des moyens de forcer Silverlight à appeler WCF de manière synchrone, mais ces approches sont épineuses, sont sujettes aux erreurs et ne valent probablement pas la peine d’être utilisées. 

Si vous ne prenez pas en compte cette problématique des appels asynchrones très tôt, cela peut réduire à néant votre architecture parfaitement organisée et vos VueModèles. En l’espace de quelques secondes, vous allez subir un véritable électrochoc qui vous laissera groggy si vous n’êtes pas vigilant. En fait, la raison à cela se résume à un petit problème tout simple : les méthodes qui appellent WCF ne peuvent pas avoir de valeurs de retour. En d’autres termes, toute méthode qui appelle une méthode asynchrone doit retourner « void ». Vous pensez probablement que c’est trop gros pour être vrai. Pourtant, c’est la stricte vérité. 

Le listing 1 présente du code Silverlight servant à appeler la méthode LoadById pour un service WCF nommé PersonService. Notez qu’il faut s’abonner à l’événement LoadByIdCompleted suivi d’un appel à LoadByIdAsync. LoadByIdAsync a le type de retour void et lorsque vous l’appelez, le retour est immédiat et vous n’accédez pas à la valeur de retour du service WCF tant que l’événement LoadByIdCompleted ne se déclenche pas.

Llisting 1 :

public void LoadById(int id)
{
// Create an instance of the service proxy
var client = new PersonService.PersonServiceClient();
// Subscribe to the “completed” event for the service method
client.LoadByIdCompleted +=
new EventHandler<PersonService.LoadByIdCompletedEventArgs>(
client_LoadByIdCompleted);
// Call the service method
client.LoadByIdAsync(id);
}
void client_LoadByIdCompleted(object sender,
PersonService.LoadByIdCompletedEventArgs e)
{
// This method is called after the call to LoadByIdAsync() is finished
PersonService.PersonDtopersonReturnedByService = e.Result;
// …
}

Prenons le temps d’assimiler ce fait. Il n’y a pas de connexion entre l’appel qui demande l’appel WCF et la logique qui gère la réponse. Par conséquent, si cette méthode LoadById faisait partie d’un Repository intitulé WcfPersonRepository, cette déconnexion signifie que vous ne pouvez pas retourner un objet de Modèle de personne rempli à partir de cette méthode car vous n’avez pas les données. C’est à ce stade que cela devient encore plus épineux : tout ce qui appelle quelque chose qui, à n’importe quel moment, appelle une méthode WCF asynchrone ne va pas pouvoir retourner autre chose que void. 

Waouh ! Cela va assurément mettre à mal votre belle architecture en couches ?

A ce stade, deux possibilités s’offrent à vous : vous pouvez décider que toutes les applications Silverlight doivent être des montagnes de code spaghetti mononiveau avec tous les éléments entassés dans une poignée de classes ViewModel non maintenables ou vous pouvez trouver un moyen de prendre en charge activement le côté asynchrone. 

Ma solution est une classe ReturnResult<T> (Remarque : Il existe un autre moyen de résoudre le problème en employant Reactive Extensions for .NET, disponible cette adresse). ReturnResult<T> sert de passerelle entre la méthode qui demande la logique WCF et la méthode qui gère les résultats de l’appel WCF. Cela permet au gestionnaire WCF « complete » de retourner des valeurs ou des exceptions à l’appelant d’origine tout en restant asynchrone. 

Dans le listing 1, je préférerais avoir une signature de méthode du type public IPersonLoad ById(int id), mais elle contient un appel WCF asynchrone et ne peut donc pas retourner de valeur utile. Avec ReturnResult<T>, vous pouvez aboutir à un résultat s’apparentant au fait de retourner un IPerson en introduisant un argument de méthode du type ReturnResult<IPerson>, comme illustré ici :

public void LoadById(ReturnResult<IPerson> callback, int id)
{
  // Create an instance of the service proxy
  var client = new PersonService.PersonServiceClient();

  // Subscribe to the « completed » event for the service method
  client.LoadByIdCompleted +=
    new EventHandler<PersonService.LoadByIdCompletedEventArgs>(
    client_LoadByIdCompleted);
  // Call the service method
  client.LoadByIdAsync(id, callback);
}

Dans la méthode LoadById, lorsque vous êtes prêt à appeler LoadByIdAsync sur le proxy de service, au lieu de juste passer l’Id à charger, vous passez également la variable ReturnResult<IPerson> callback en tant qu’état utilisateur. Tous les appels asynchrones fournissent une surcharge de méthode qui prend une variable de type object et nommée userState. Ce paramètre userState vous permet de passer dans l’appel asynchrone des données qui seront ensuite disponibles une fois l’événement terminé via CompleteEventArgs. 

Le listing 2 présente le code pour le gestionnaire d’événement LoadByIdCompleted. Remarquez qu’il récupère la variable ReturnResult<IPerson> callback en accédant à e.UserState. Cette méthode de gestionnaire d’événement dispose maintenant d’un moyen pour communiquer avec l’appelant original de sorte qu’elle peut retourner une instance remplie de IPerson ou une exception en appelant la méthode Notify. 

Listing 2 :

void client_LoadByIdCompleted(object sender,
PersonService.LoadByIdCompletedEventArgs e)
{
var callback = e.UserStateasReturnResult<IPerson>;
if (e.Error != null)
{
// Pass the WCF exception to the original caller
callback.Notify(e.Error);
}
else
{
PersonService.PersonDtoperson ReturnedByService = e.Result;
var returnValue = new Person();
var adapter = new PersonModelToServiceDtoAdapter();
adapter.Adapt(personReturnedByService, returnValue);
// Pass the populated model to the original caller
callback.Notify(returnValue);
}

Le listing 3 montre un exemple de code d’une classe ViewModel qui appelle la méthode Repository LoadById(ReturnResult<IPerson>, int). Lorsque la classe ViewModel effectue l’appel, elle crée une instance de ReturnResult<IPerson> et la passe sous forme de délégué à une méthode dans la VueModèle qui gérera la réponse asynchrone de l’appel Repository. Dans ce cas, il s’agit d’une méthode LoadCompleted, qui est l’endroit où la VueModèle consomme les données IPerson récupérées.

Listing 3 :

private void Load()
{
m_Repository.LoadById(
new ReturnResult<IPerson>(LoadCompleted),
IdToLoad.Value);
}
private void LoadCompleted(ReturnResult<IPerson> callback)
{
if (callback.Error != null)
{
ShowMessage(callback.Error);
}
else
{
var adapter = new PersonModelToViewModelAdapter();
adapter.Adapt(callback.Result, this);
m_Model = callback.Result;
}
}

Comme vous pouvez le voir, en utilisant le modèle Repository, tous les détails du mode de chargement de IPerson et de l’emplacement de son chargement sont abstraits dans une autre classe. La seule chose dont la VueModèle doit se préoccuper est la gestion des éventuelles erreurs ou le fait que les données lui soient passées via la variable ReturnResult<IPerson> callback.

Un effet de bord pratique de l’utilisation de Repository tient au fait que les objets de transfert de données proxy de service WCF peuvent être entièrement encapsulés au sein de la classe Repository proprement dite. Aucune autre classe ne doit référencer les classes générées par Add Service Reference. Sans une solution au problème d’appel asynchrone, la VueModèle devrait en savoir nettement plus sur le mode de récupération des données IPerson, ce qui entraînerait une violation de la séparation des différents aspects.

Téléchargez cette ressource

Guide de Sécurité IA et IoT

Guide de Sécurité IA et IoT

Compte tenu de l'ampleur des changements que l'IA est susceptible d'entraîner, les organisations doivent élaborer une stratégie pour se préparer à adopter et à sécuriser l'IA. Découvrez dans ce Livre blanc Kaspersky quatre stratégies efficaces pour sécuriser l'IA et l'IoT.

Tech - Par Renaud ROSSET - Publié le 12 juillet 2012