Requêtes http et violation de threads

L’une des principales caractéristiques d’un smartphone tel que Windows Phone 7 est d’être constamment connecté à Internet. Partant de ce fait, il est tout naturel, qu’en tant que développeurs, nous cherchions à exploiter les nombreux avantages qu’offre cette connexion Internet, en interagissant par exemple avec un serveur de base de données. Seulement, sur le web, de nombreuses technologies cohabitent et il se peut que votre serveur ne puisse communiquer que via des requêtes http (envoi des données en GET ou POST). Bien que très simple d’utilisation, l’objet WebClient présente tout de même quelques limites. L’alternative à ce dernier est d’utiliser l’objet « httpWebRequest », qui est bien plus complet (meilleur gestion des cookies). Néanmoins, les appels asynchrones que ce dernier effectue empêchent d’interagir directement avec l’interface utilisateur : une exception de type « invalid cross-thread access » est levée.

 

Pourquoi une exception est-elle déclenchée ?

Ce qu’il faut savoir en premier lieu est que Windows Phone 7 (et il en va de même pour Silverlight) ne permets d’effectuer que des appels asynchrones. Partant de ce constat, il est important de souligner que le fait de lancer une requête http en asynchrone lance un nouveau fil d’exécution (appelé « Thread ») pour votre application, en plus du Thread d’exécution principal. Désormais toute fonction appelée par le Thread secondaire va rester dans ce Thread. Prenons un exemple concret pour illustrer tout ça :

private static HttpWebRequest _webRequest;

private static HttpWebResponse _reponse;

 

public void LancerAppelAsynchrone()

{

     maProgressBar.Visibility = System.Windows.Visibility.Visible;

     Uri AdresseAppel = new Uri("http://www.adresseDeVotreAppel.com");

 

     _webRequest = (HttpWebRequest)WebRequest.Create(AdresseAppel);

     _webRequest.Method = "GET";

 

     _webRequest.BeginGetResponse(FinReceptionDonneesAsync, _webRequest);

}

private void FinReceptionDonneesAsync(IAsyncResult asynchronousResult)

{

    maProgressBar.Visibility = System.Windows.Visibility.Collapsed;

}

 

Le fait de vouloir cacher notre ProgressBar va donc systématiquement lancer une exception de type « invalid cross-thread access », car les éléments de l’interface ont été instanciés dans le thread principal et dépendent donc de ce dernier. Or la fonction « FinReceptionDonneesAsync » dépends du thread secondaire, ayant été lancé de l’appel à la méthode « BeginGetResponse ». Nous ne pouvons tout simplement pas interagir avec un objet instancié dans un thread qui n’est pas celui dans lequel nous tentons de masquer notre ProgressBar. Là encore afin de mieux comprendre, je vous invite à jeter un coup d’œil à l’illustration ci-dessous.

schema threads

 

Comment faire pour régler ce problème ?

Il suffit d’indiquer à votre programme que l’instruction qui cache la ProgressBar doit être exécutée dans le Thread principal. Pour ce faire, vous pouvez utiliser la petite classe ci-dessous.

public static class UIThread

{

     private static readonly Dispatcher Dispatcher;

 

     static UIThread()

     {

         Dispatcher = Deployment.Current.Dispatcher;

     }

 

     public static void Invoke(Action __action)

     {

         if (Dispatcher.CheckAccess())

            __action.Invoke();

         else

            Dispatcher.BeginInvoke(__action);

     }

}

 

Le constructeur de cette classe statique va stocker une référence au Dispatcher pour l’application exécutée dans la variable membre de la classe.

La méthode « Invoke » va vérifier si l’action que vous tentez d’exécuter est dans le Thread principal ou non. Si c’est le cas, elle va simplement exécuter l’instruction demandée, sinon elle va la lancer dans le Thread principal, dont la référence a été stockée au préalable par le constructeur.

Désormais, il nous suffit simplement d’utiliser notre classe de la façon suivante, afin de cacher notre ProgressBar sans encombre :

private void FinReceptionDonneesAsync(IAsyncResult asynchronousResult)

{

UIThread.Invoke(() => maProgressBar.Visibility = System.Windows.Visibility.Collapsed);

}

 

Limites de cette méthode

Dans la majorité des cas d’utilisation, la solution proposée ci-dessus convient tout à fait. Elle est simple et très facile à mettre en place dans vos applications. Néanmoins elle présente certaines limites et ne permet pas de tout faire. Elle ne permet pas, par exemple, d’instancier un nouvel objet d’interface utilisateur (prenons l’exemple d’un simple TextBlock). Ainsi, le code suivant va lever la même exception que précédemment.

private void FinReceptionDonneesAsync(IAsyncResult asynchronousResult)

{

     TextBlock tbl = new TextBlock();

}

 

Bien entendu, le plus logique serait d’utiliser notre classe « UIThread », ce qui donnerait ce qui suit :

private void FinReceptionDonneesAsync(IAsyncResult asynchronousResult)

{

    TextBlock tbl;

    UIThread.Invoke(() => tbl = new TextBlock());

}

 

Néanmoins, votre objet sera toujours « null », il n’est pas possible d’instancier un objet quel qu’il soit avec UIThread. Il convient donc d’utiliser le code suivant :

private void FinReceptionDonneesAsync(IAsyncResult asynchronousResult)

{

    using (AutoResetEvent are = new AutoResetEvent(false))

    {

         Deployment.Current.Dispatcher.BeginInvoke(() =>

         {

              TextBlock tbl = new TextBlock();

              tbl.Text = "Ca marche !";

              LayoutRoot.Children.Add(tbl);

              are.Set();

          });

          are.WaitOne();

     }

}

Le code ci-dessus permet de mettre en pause le Thread principal et donc d’exécuter le code contenu dans la méthode « BeginInvoke() », pendant que ce dernier est en stand-by. Cela permet donc d’avoir accès aux contrôles de l’interface utilisateur et d’éviter les exceptions de violation de Threads.


Vous pouvez désormais instancier de nouveaux objets et interagir avec l’interface utilisateur de votre application, tout en effectuant des requêtes http (ou tout autre appel asynchrone levant un « invalid cross-thread access »).

© 2011 Copyright cigo-developpement.fr