[WP7Dev] Utiliser le WebClient avec les Reactive Extensions pour Télécharger en Asynchrone

This article is available in english.

Il y a un framework très intéressant qui s'est glissé dans le SDK pour Windows Phone 7 : Les Reactive Extensions.

C'est en fait un framework assez mal compris, principalement parce qu'il n'est pas simple à maitriser, mais que lorsque vous avez pris la main dessus, il est très très pratique ! J'apprécie particulièrement l'extension MemoizeAll, qui est très utile.

Mais je m'égare.


Un Téléchargement de Chaine de caractère Non-Réactif

Sur le Windows Phone 7, la class WebClient n'a qu'une méthode DownloadStringAsync et a l'évènement DownloadStringCompleted correspondant. Cela veut dire que l'on est forcé d'être asynchrone, pour être cordial avec l'interface graphique et ne pas geler l'application pour l'utilisateur, à cause d'une mauvaise pratique de programmation en étant synchrone sur les appels distants.

Dans un monde sans Reactive Extensions, on aurait quelque chose comme ceci :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void StartDownload()
{
var wc = new WebClient();
wc.DownloadStringCompleted +=
(e, args) => DownloadCompleted(args.Result);

// Démarrer le téléchargement
wc.DownloadStringAsync(new Uri("http://www.data.com/service"));
}

public void DownloadCompleted(string value)
{
myLabel.Text = value;
}

Très simple. Mais rapidement vous allez vous rendre compte que l'exécution de l'évènement DownloadStringCompleted est effectué sur la Thread de l'interface graphique. Cela veut dire que, si pour quelque raison vous avez besoin de faire un long calcul suite à la réception de la chaine, vous allez geler l'interface utilisateur pour la durée du calcul. Et comme Windows Phone 7 est fait pour la fluidité et que vous ne voulez pas être le mauvais élève, vous allez vouloir mettre le calcul dans la queue du ThreadPool.

Mais vous allez aussi avoir à mettre à jour l'interface graphique dans le Dispatcher, donc vous avez besoin de revenir du ThreadPool.

On aura alors:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public void StartDownload()
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted +=
(e, args) => ThreadPool.QueueUserWorkItem(d => DownloadCompleted(args.Result));

// Démarrer le téléchargement
wc.DownloadStringAsync(new Uri("http://www.data.com/service"));
}

public void DownloadCompleted(string value)
{
// Quelques calculs très longs
Thread.Sleep(1000);

Dispatcher.BeginInvoke(() => myLabel.Text = value);
}

C'est un peu plus complexe. Et on remarquera ensuite qu'il faut gérer les exceptions parceque, en fait, c'est le Web. C'est finalement peu fiable.

Donc, ajoutons la gestion des exceptions :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void StartDownload()
{
WebClient wc = new WebClient();

wc.DownloadStringCompleted += (e, args) => {
try {
ThreadPool.QueueUserWorkItem(d => DownloadCompleted(args.Result));
}
catch (WebException e) {
myLabel.Text = "Error !";
}
};

// Démarrer le téléchargement
wc.DownloadStringAsync(new Uri("http://www.data.com/service"));
}

public void DownloadCompleted(string value)
{
// Quelques calculs très longs...
Thread.Sleep(1000);
Dispatcher.BeginInvoke(() => myLabel.Text = value);
}

Cela commence à devenir relativement complexe. Mais maintenant, vous devez attendre le résultat d'un autre appel de WebClient et afficher les deux résultats.

Aïe. Ok, je vais vous épargner celui la.


Le Même Exemple avec les Reactive Extensions

Les Reactive Extensions traitent les évènements asynchrones comme un flux d'évènements. On souscrit à ce flux et on s'en va, et on laisse le Reactive Framework faire la besogne.

Je vais vous épargner les explications sur la dualité entre IObservable et IEnumerable, parce que Erik Meijer l'explique particulierement bien.

Donc, je recommence avec l'exemple simple, et après avoir ajouté les références vers System.Observable et System.Reactive, on peut télécharger une chaîne de caractères :

1
2
3
4
5
6
7
8
9
10
11
12
public void StartDownload()
{
WebClient wc = new WebClient();

var o = Observable.FromEvent<DownloadStringCompletedEventArgs>(wc, "DownloadStringCompleted")

// Quand l'évènement est levé, on sélectionne la chaine et
// on en fait un IObservable<string> à la place
.Select(newString => newString.EventArgs.Result);

// Souscription à l'observable, et on assigne le texte du label
o.Subscribe(s => myLabel.Text = s);


// Démarrage du téléchargement
wc.DownloadStringAsync(new Uri("http://www.data.com/service"));
}

Cet exemple fait la même chose que le premier exemple. Vous remarquerez l'utilisation de Observable.FromEvent qui permet de transformer un évènement en observable d'évènements. Dans cet exemple, le flux d'évènements ne va en contenir en fait qu'un seul, puisque la fin du téléchargement ne s'effectue qu'une seule fois. Chacune de ces occurrences de l'évènement est alors projetée en utilisant Select, vers une chaîne de caractères qui représente le résultat de la requête web.

Cet exemple simple est un peu plus complexe à cause de la plomberie.

Mais maintenant, on veut supporter les changement de contexte de Threads. Les Reactive Extensions supportent le concept de Scheduler, pour observer un IObserable dans un contexte spécifique.

Don, on peut utiliser un Scheduler comme ceci.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public void StartDownload()
{
WebClient wc = new WebClient();

var o = Observable.FromEvent<DownloadStringCompletedEventArgs>(wc, "DownloadStringCompleted")

// On s'assure que l'on est sur le ThreadPool
.ObserveOn(Scheduler.ThreadPool)

// Lorsque l'evenement est levé, on selectionne la chaîne
.Select(newString => ProcessString(newString.EventArgs.Result))

// Maintenant on retourne sur la Thread graphique
.ObserveOn(Scheduler.Dispatcher)

// On souscrit à l'observable et on assigne le text au label
.Subscribe(s => myLabel.Text = s);

wc.DownloadStringAsync(new Uri("http://www.data.com/service"));
}

public string ProcessString(string s)
{
// Un très très long calcul...
return s + "1";
}

Dans cet exemple, on a changé de contexte deux fois pour nos besoins, et maintenant, le code est moins complexe que l'exemple original/

Et maintenant, si l'on veut ajouter la gestion des exceptions :

1
    .Subscribe(s => myLabel.Text = s, e => myLabel.Text = "Erreur ! " + e.Message);

Et vous l'avez :)

Combiner le Résultat de deux Téléchargements

Combiner le résultat de deux opérations asynchrones peut être assez complexe, et vous avez à gérer les exceptions, les rendez-vous et des états complexes. Je n'écrirais pas d'exemple ici, promis, mais je vais vous donner un exemple en utilisant les Reactive Extensions :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

public IObservable<string> StartDownload(string uri)
{
WebClient wc = new WebClient();

var o = Observable.FromEvent<DownloadStringCompletedEventArgs>(wc, "DownloadStringCompleted")

// On s'assure qu'on l'on est pas sur la Thread graphique
.ObserveOn(Scheduler.ThreadPool)

// On transforme l'évènement en chaine de caractères
.Select(newString => ProcessString(newString.EventArgs.Result));

wc.DownloadStringAsync(new Uri(uri));

return o;
}

public string ProcessString(string s)
{
// Un calcul très très long !
return s + "<!-- Processing End -->";
}

public void DisplayMyString()
{
var asyncDownload = StartDownload("http://bing.com");
var asyncDownload2 = StartDownload("http://google.com");

// On prend les deux résultats et on les combine lorsqu'ils sont disponibles
var zipped = asyncDownload.Zip(asyncDownload2, (left, right) => left + " - " + right);

// On revient sur la thread principale
zipped.ObserveOn(Scheduler.Dispatcher)

// On souscrit à la chaine et on l'affiche
.Subscribe(s => myLabel.Text = s);
}

Et vous obtiendrez une intéressante combinaison de Google et Bing :)

Publié jeudi 24 juin 2010 10:32 par jay
Classé sous , , , ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires


Les 10 derniers blogs postés

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01

- SharePoint Online: Script PowerShell pour supprimer une colonne dans tous les sites d’une collection par Blog Technique de Romelard Fabrice le 11-27-2018, 18:01