Je continue avec l’affichage des Images et je vous propose cette fois de voir les implémentations possibles pour récupérer des images depuis le Web avec le protocole http.
L’implémentation classique :
public static void GethttpImage1(string urlImage, Action<BitmapImage> action) { var request = (HttpWebRequest)WebRequest.Create(urlImage); request.BeginGetResponse(result => { using (var sr = request.EndGetResponse(result)) { Deployment.Current.Dispatcher.BeginInvoke(() => { var image = new BitmapImage(); image.SetSource(sr.GetResponseStream()); if (action != null) action(image); sr.Close(); }); } } }
urlImage contient l’url de l’image sous forme d’une châine de caractère : exemple : http://dev.bratched.fr/download/testimage1.png
La méthode utilise un WebRequest et lit la réponse de la requête sous forme d’un stream avec le request.EndGetResponse.
La partie
Deployment.Current.Dispatcher.BeginInvoke(() =>
est très importante car elle va permettre d’utiliser le BitmapImage dans le Thread Principal même lorsque la méthode est appelée dans un Thread tournant en tâche de fond.
L’appel de la méthode retourne donc l’image dans une action.
Elle pourra ensuite être appelée de cette façon pour alimenter une propriété de type BitmapImage :
HttpImageService.GethttpImage1("http://dev.bratched.fr/download/testimage1.png", b => { Image = b; });
L’implémentation avec Async / Await
Microsoft fournit une bibliothèque « Microsoft Async » qui va permettre d’utiliser le Async/Await dans des méthodes courantes de Windows Phone
Ce paquet va permettre l’utilisation du WebRequest de façon simplifiée et de retourner un Task<BitmapImage>
using (WebResponse response = await request.GetResponseAsync())
remplace les éléments ci dessus
request.BeginGetResponse(result => { using (var sr = request.EndGetResponse(result)) {
La fonction retourne directement une tache de type BitmapImage (à la place du retour par action)
public static async Task<BitmapImage> GethttpImage2(string urlImage) { try { var request = (HttpWebRequest)WebRequest.Create(urlImage); using (WebResponse response = await request.GetResponseAsync()) { BitmapImage image = null; DispatcherSynchronizationContext dsc = new DispatcherSynchronizationContext(Deployment.Current.Dispatcher); await Task.Factory.StartNew(() => { dsc.Send(_ => { image = new BitmapImage(); image.SetSource(response.GetResponseStream()); }, null); }); return image; } } catch (Exception) { return null; } }
Remarque importante :
DispatcherSynchronizationContext dsc = new DispatcherSynchronizationContext(Deployment.Current.Dispatcher); await Task.Factory.StartNew(() => { dsc.Send(_ => {
remplace l’ancien
Deployment.Current.Dispatcher.BeginInvoke(() =>
C’est toujours le même objectif : Utiliser le Thread Principal obligatoire pour BitmapImage dans le cas d’appel dans un thread tournant en tâche de fond.
La syntaxe utilisée ici est plus à la mode et permet une gestion plus fine du système de synchronisation entre Thread. Vous pouvez la remplacer sans soucis par l’ancien code.
L’utilisation de cette fonction se fait de la façon suivante : (Image étant une propriété de type BitmapImage)
Image = await HttpImageService.GethttpImage2("http://dev.bratched.fr/download/testimage1.png");
HttpClient
Il faut ajouter le paquet Nuget « Microsoft HTTP Client Libraries » pour pouvoir utiliser la classe HttpClient. L’avantage de ce paquet est de proposer une syntaxe identique à l’implémentation Windows 8.
L’implémentation ressemble ensuite à celle-ci.
Remarque importante, il est obligatoire de passer par un buffer au lieu du Stream proposé car :
- BitmapImage nécessite d’être utilisé dans le Thread principal
- Le Thread principal ne peut pas être utilisé dans le AsyncStream
public static async Task<BitmapImage> GethttpImage3(string urlImage) { try { HttpClient httpClient = new HttpClient() { MaxResponseContentBufferSize = 100000000 }; var ImageData = await httpClient.GetByteArrayAsync(urlImage); { using (var s = new MemoryStream(ImageData)) { BitmapImage image = null; DispatcherSynchronizationContext dsc = new DispatcherSynchronizationContext(Deployment.Current.Dispatcher); await Task.Factory.StartNew(() => { dsc.Send(_ => { image = new BitmapImage(); image.SetSource(s); }, null); }); return image; } } } catch { return null; } }
L’utilisation de cette fonction ce fait de la même façon que précédemment :
Image = await HttpImageService.GethttpImage3("http://dev.bratched.fr/download/testimage1.png");
Tests de performance.
Les 3 méthodes sont utilisables mais quelle est la méthode la plus performante dans le cas d’un chargement de plusieurs images ?
J’ai adapté le précédent programme de Tests de performances d’images statiques pour
- afficher les 3 méthodes dans des listes
- faire un Benchmark en chargeant 10 fois la grosse image png (400ko 1000×1000) du précédent article.
J’ai également ajouté le MemoryCounter de Coding4Fun afin de visualiser la consommation et libération mémoire.
Voici les résultats sur un réseau Wifi (moyennes sur 20 passages).
- Méthode 1 : Classique HttpRequest/Response avec Action : 6 sec
- Methode 2 : Avec Async HttpRequest/Response : 9 sec
- Methode 3 : Avec HttpClient : 8,5 sec
Durant les tests, j’ai également inversé l’ordre de passage pour ne pas influencer les résultats.
La consultation des listes donne un réel aperçu des 3 différentes méthodes.
C’est encore une fois la méthode « classique » avec HttpRequest qui donne le meilleur résultat.
Le système d’Action permet d’éviter l’attente de la fin du traitement du thread pour interroger le réseau et permet ainsi une meilleure parallélisassions des tâches.
Le code Source est accessible ici