Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Silverlight: Sérialiser les tâches asynchrones

[new:30/06/2011]L’asynchronisme s’installe durablement dans les applications : multitâche rendu obligatoire pour bénéficier des progrès des nouveaux processeurs et communications asynchrones sont deux ingrédients qu’une application Silverlight doit gérer au minimum. Hélas le développeur raisonne de façon “synchrone” beaucoup plus facilement que de façon asynchrone... Comment simplifier le développement et la maintenance des applications modernes tout en respectant cet asynchronisme qui s’immisce de gré ou de force dans la logique des programmes ?Plus...

Faut-il vraiment bruler le pattern MVVM ?

Le pattern MVVM j’en parle assez souvent ici, même au travers de très gros articles à télécharger. Je vous ai déjà présenté plusieurs façons d’appliquer ce pattern juste en Csharp/Xaml ou bien avec des librairies comme MVVM Light. Il est temps de faire un point sur la mise en pratique. Peut-on aller plus loin dans le “light” ?Plus...

MVVM, je m’y perds ! [une réponse rapide]

[new:16/09/2010]MVVM est une pattern, une simple pattern, pas une technologie nouvelle. Elle est utilisable dans de nombreux contextes, sous ce nom ou un autre et sous des formes plus ou moins identiques. Rien de nouveau donc. Et pourtant tout change. “Je m’y perds” est une réflexion que j’entends souvent. Encore aujourd’hui, un lecteur de Dot.Blog me faisait part de ce sentiment d’être un peu “embrouillé”. Alors plutôt qu’une réponse en privé qui ne profiterait qu’à un seul, voici la réponse à ce lecteur, et bien sûr, à tous les autres qui partagent la même impression…Plus...

UX != UI

[new:11/6/2010]Une petite mise au point s’impose devant l’abus qui commence a être fait de l’acronyme “UX” et de son développé “User Experience” (Expérience Utilisateur). L’UX n’est pas l’UI !Plus...

NDepend 3 : votre code est une base de données en or…

[new:10/6/2010]NDepend est un outil très intelligent dont je vous ai déjà parlé (voir le billet Visual NDepend un outil d'analyse de code original et puissant ainsi que l’allusion directe qui y est faite dans Le retour du spaghetti vengeur). La version 3 est désormais totalement intégrée à l’IDE de VS 2010 ce qui rend son utilisation encore plus simple, directe et rapide. NDepend transforme votre code en une mine d’or ! Tout code, projet, solution devient une base de données qui peut être interrogée et obtenir une vision totalement neuve sur ses forces … et ses défauts !Plus...

Deux règles pour programmer Silverlight & WPF

Des règles et des bonnes pratiques pour développer des applications Silverlight il en existe bien plus que deux, faire croire le contraire ne serait pas honnête. Ne serait-ce que par la richesse des EDI utilisés (Visual Studio, Expression Blend, Expression Design…) il faut accumuler de l’expérience et mémoriser de nombreuses patterns pour architecturer et designer correctement une application.

Alors, en vrac, voici deux règles qui me passent par la tête et que je voulais vous communiquer:

Programmation par Templates

Sous Silverlight lâchez les vieilles habitudes. L’une de celles-ci que je rencontre souvent est celle qui consiste à se jeter sur son clavier pour créer un UserControl (ou un Control par héritage) dès qu’on a besoin d’un nouveau composant qui semble absent de la bibliothèque.

Erreur. Méthode du passé.

En effet, Silverlight et WPF impliquent des changements radicaux de mode de pensée et d’habitudes. Sous ces environnements, la majorité des besoins sont couverts par les composants existants, il “suffit” juste d’en modifier le template.

Nous sommes passés d’une programmation par héritage à une programmation par modèle (template).

Deux exemples pour bien cerner ce que j’entends par là.

Sticky Note

Prenons pour premier exemple un cas d’école, déjà ancien mais en plein dans notre propos : sticky notes pour WPF (sticky notes listbox). Ce “composant” se présente comme une liste verticale de petites notes de type “post-it” légèrement décalées les unes par rapport aux autres.

L’effet est sympathique et donne un peu de fantaisie et de vie à ce qui ne serait qu’une liste rectangulaire dans une grille en programmation classique.

Ce magnifique “composant” n’utilise aucun code “'classique”. Inutile dans les sources de chercher la classe StickyNotes qui hériterait de Control ou même de chercher dans les fichiers de l’application le UserControl créé pour l’occasion.

Rien de tout cela.

Sticky Notes est créé uniquement en jouant sur le templating d’une simple… Listbox ! Une poignée de Xaml et des templates, c’est tout.

Language button

Dernièrement dans une application Silverlight je devais implémenter un bouton permettant de choisir depuis la page d’accueil la langue à utiliser (français ou anglais). Forcément on pense à un ToggleButton ou quelque chose d’équivalent (ce qui n’existe pas de base). On pourrait aussi associer deux RadioButton dans une grille.

Mais le plus intéressant consiste à se demander “quel control existant se rapproche le plus du comportement que je souhaite implémenter”. En y réfléchissant quelques secondes on s’aperçoit assez vite qu’une CheckBox possède deux états stables (oublions ici l’état indéterminé). Ce composant comporte toute la logique et les états visuels permettant de gérer deux états.

Par défaut une CheckBox c’est une case à cocher avec un bout de texte devant ou derrière.

Le plus dur consiste à se l’imaginer comme deux drapeaux (français et US) côte à côte, celui qui est sélectionné étant à 110% de sa taille et l’autre à 50%. Par convention arbitraire et chauvinisme inconscient certainement j’ai considéré que IsChecked = True était le Français, à False l’anglais. En partant d’une CheckBox que j’ai totalement vidée de son contenu, et en ajoutant les deux drapeaux (et quelques autres ingrédients et animations via le VSM) j’ai obtenu un merveilleux “ToggleButton” sans jamais “sous-classer” le moindre contrôle. Juste en écrivant un template.

Le ViewModel de la page d’accueil offre une propriété IsFrench de type booléen qui est simplement bindée à la propriété IsChecked du CheckBox (en mode TwoWay) et l’affaire est jouée !

Règle 1 : De l’héritage au templating

La programmation Xaml (Silverlight / WPF) est une programmation visuelle qui s’effectue par templating. Créer des UserControl et encore plus sous-classer des contrôles existants devient quelque chose de rarissime.

Nous sommes passés de l’ère de la programmation objet par héritage à celle de la programmation visuelle par templating. Bien comprendre toute la signification de ce changement est un point primordial et un préliminaire indispensable pour comprendre cette technologie et donc la programmer intelligemment.

Programmation par Properties

Il s’agit du même genre de glissement, une pente douce mais dont la longueur finit par conférer une vitesse tellement grande à celui qui s’y laisse glisser que le décor n’a plus rien à voir avec celui qu’on a tout le temps d’admirer en balade à dos d’âne…

Dans l’exemple précédent on touchait du doigt cette nouvelle approche mais sans la mettre en exergue.

En effet, dans la pattern M-V-VM plutôt que de créer un code incompatible avec le visuel d’un côté pour ensuite créer de l’autre des tripotés de convertisseurs (programmation classique non M-V-VM sous WPF et Silverlight dans une moindre mesure) il semble bien plus simple d’exposer dans les ViewModels des propriétés directement exploitables par l’UI. Si adaptation des données il doit y avoir c’est le ViewModel qui s’en charge (directement si cela est ponctuel, ou via une classe de service si la chose doit être réutilisées ailleurs).

Dans l’exemple précédent du Checkbox transformé en ToggleButton de langue, aucun événement n’est programmé, aucun gestionnaire n’est écrit pour réagir au clic. Tout se joue dans le ballet automatique de IsChecked de la CheckBox et de IsFrench du ViewModel sous l’égide discrète mais indispensable d’un binding two way…

Quant l’utilisateur clique sur la CheckBox (enfin sur le ToggleButton avec les deux drapeaux) le composant sous-jacent bascule sa propriété IsChecked à vrai ou faux selon le cas. Comme cette dernière est liée à IsFrench du ViewModel, une propriété de type booléen aussi pour assurer la compatibilité des comportements, le ViewModel reçoit pas un événement mais l’une de ces propriétés (IsFrench) se voit modifiée. Ce qui déclenche le Setter de cette dernière. Ce dernier s’occupant de modifier le fichier de ressource utilisé pour puiser les chaines de caractères. De là et par une série de notification de changement de propriétés, il avertit en retour la Vue que toutes les propriétés de type texte ont été modifiées. La vue (et ses binding) y réagit en rafraichissant les affichages…

Toute cette mécanique s’est déroulée sans aucun gestionnaire d’événement, sans aucune programmation “classique” (en dehors du ViewModel et de ces propriétés gérant INotifyPropertyChanged, ce qui peut être automatisé ou simplifié en utilisant Mvvm-light par exemple).

Règle 2 :  de l’événementiel à binding

La seconde règle d’or à bien comprendre pour tirer totalement partie de Xaml et de ses enfants (Silverlight et WPF) est ainsi d’opter pour un modèle de développement de type Model-View-ViewModel se basant presque exclusivement sur des couples de propriétés mis en relation via binding.

On est passé de l’ère du développement dit “événementiel” des premières versions de Windows à ce qu’on pourrait appeler la “programmation par Binding” ou par “properties”.

Conclusion

Pour résumer :

Règle 1 on cherche à templater des contrôles existants sans créer des UserControl ni dériver des contrôles existants.

Règle 2 : on base la dynamique de l’application sur le binding entre propriétés et non plus sur les gestionnaires des événements des contrôles.

Si vous avez déjà fait vôtre ses règles alors vous allez devenir, si ce n’est pas déjà le cas, de très bons développeurs Silverlight et Xaml très recherchés !

Si vous n’aviez pas encore vu les choses sous cet angle là, je serai très heureux si par chance j’ai réussi à tirer le voile qui vous empêchait de les voir ainsi. Vous ne ferez qu’entrer plus vite dans la catégorie précédente !

Bon Dev, et, œuf corse,

Stay Tuned !

Silverlight et le multi-thread (avec ou sans MVVM)

La “parallélisation” des traitements est devenu un enjeu majeur et paradoxal du développement et Silverlight n’échappe pas à ce nouveau paradigme.

Nouveau Paradigme ?

Nouveau paradigme, cela fait toujours un peu “attention je suis en train de dire quelque chose d’intelligent et j'utilise une tournure à la mode qui fait savante pour le faire remarquer”… Mais parfois cela a aussi un vrai sens et une vraie justification !

Nouveau paradigme donc que la programmation multitâche, ou pour être encore plus chébran, parlons de programmation parallèle, voire de parallélisme tout en évitant le trop prétentieux “massivement parallèle” (nos PC ne comptent pas encore la centaine de “cores” – plutôt que “cœurs” pour faire totalement geek – qu’Intel prévoit pour dans quelques temps).

On le sait depuis un moment, la fréquence des processeurs n’évoluera plus comme elle l’a fait jusqu’à lors. Le fondeur de processeurs se retrouve ainsi face à un mur infranchissable, un peu comme l’était le mur du son pour l’aviation jusqu’à ce jour d’octobre 47 ou le pilote Yeager à bord du Bell-X-1 finit par traverser les briques de ce mur qu’on avait cru impénétrable. Pour nous il s’agit du “mur des performances”. On miniaturise, on diminue l’épaisseur des couches, on tasse les transistors, mais on butte sur la fréquence. Le seul moyen d’aller plus vite reste de multiplier les processeurs… Et comme multiplier les cartes mères atteint vite une limite en termes de prix et de consommation électrique, on essaye désormais de tasser des cœurs sur les puces comme on tassait les transistors à l’étape précédente. Ce n’est pas une option et il n’y a pas de “plan B”, c’est la seule solution jusqu’à ce qu’existent les ordinateurs quantiques (dont la faisabilité reste totalement à prouver).

Deux, quatre, huit, trente-deux, bientôt cent cœurs dans les machines de monsieur Tout-Le-Monde. 

Hélas ce n’est pas avec les techniques de programmation utilisées jusqu’aujourd’hui que les informaticiens vont s’en sortir. Les fondeurs ont trouvé le moyen de percer le “mur des performances”, et si les informaticiens ont une idée de comment ils vont relever le défi, ils ne se sont pas encore dotés des outils permettant de le faire : une formation ad hoc et une approche radicalement différente de la programmation.

On peut ainsi réellement, et sans emphase exagérée, parler de “nouveau paradigme” de la programmation parallèle car seule cette nouvelle approche va permettre aux logiciels de bénéficier des accélérations offertes par les processeurs modernes et encore plus par ceux à venir. Et pour cela il va falloir que les développeurs se mettent à niveau alors qu’ils sont déjà à la traîne de cette révolution…

J’ai dernièrement acquis une machine à 4 cœurs hyperthreadés, donc à 8 cœurs apparents. Avec certaines applications elle va à peine aussi vite que mon double cœurs E6700 overclocké (et pas toujours)… Je regarde la jolie fenêtre des performances de Windows, je vois ces huit magnifiques graphiques qui sont autant de preuves formelles de la puissance de la bête (et un enchantement de geek), mais un seul s’active vraiment, un autre le faisant mollement, et les 6 autres piquant un roupillon désespérant ! Ces applications je les maudis et dans la foulée leurs concepteurs incompétents qui ont programmé comme on le faisait il y a 20 ans : en pensant qu’un seul processeur anime la machine et que Windows va se débrouiller pour partager le temps (et que si l’utilisateur trouve que “ça rame” il n’a qu’à acheter une machine récente…).

En revanche je suis plein de compliments confraternels à l’égard des développeurs qui ont programmé les quelques applications qui savent se servir de mes huit cœurs ! Ces programmes qui s’essoufflaient sur le E6700 deviennent des bombes sur le i870, ou, au minimum de mes attentes, vont nettement plus vite, ce qui n’est déjà pas si mal.

Faire partie de ceux qu’on maudit ou de ceux qu’on félicite, il va falloir choisir son camp !

Programmer “parallèle” devient ainsi une obligation de fait, techniquement parlant. Mais il ne faut pas ignorer non plus les cas plus classiques où le parallélisme est le seul moyen de programmer certaines fonctions. C’est ce que nous verrons dans un instant au travers d’un exemple de code.

Mais paradoxal !

En effet, ce nouveau paradigme n’en est pas moins paradoxal . La raison est simple : d’un côté les multi-cœurs se généralisent dans les PC et de l’autre l’informatique mobile explose avec des téléphones aux interfaces graphiques à faire pâlir les progiciels PC les plus chers, des iPad, des EEEPC, et autres merveilleuses petites machines ayant toutes en commun d’être dotées d’un seul processeur rikiki et monocœur !

Alors que .NET 4.0 arrive avec des dizaines de méga de couches plus sophistiquées les unes que les autres, simultanément Silverlight et son mini Framework sont annoncés (avec XNA) comme l’interface des Phones 7, voire bientôt de certaines tablettes PC…

Faire le grand écart entre ces deux tendances extrêmes et néanmoins concomitantes relève du tour de force et place l’informaticien dans une situation paradoxale. Et pourtant ce sont deux défis que le développeur doit dès aujourd’hui relever, et simultanément ! Même en s’y mettant tout de suite, il part en retard (les “utilisateurs de base” possèdent depuis plusieurs années des machines à double cœurs !). Il faut donc programmer en visant la “scalability” ou “capacité à s’adapter”, s’adapter à la charge, s’adapter au matériel. Tirer le meilleur d’un Phone 7, faire cracher ses chevaux à un PC de bureau 8 cœurs. En gros il faut maîtriser la programmation parallèle.

En pratique on fait comment ?

On sort ses neurones du mode économie d’énergie certifié Energy Star et on se sert de l’ordinateur le plus massivement parallèle de l’univers : son cerveau :-)

Cela signifie adopter une nouvelle “attitude” face à la programmation quotidienne. La moindre classe doit être pensée dans un contexte multi-tâche. Cela doit devenir un automatisme, comme de finir chaque instruction par un point-virgule en C#.

Mais comme il n’est pas question dans ce blog de faire un cours complet sur le multi-tâche, attardons-nous sur un exemple concret et voyons comment introduire un peu de ces réflexes qui tendent vers une meilleur utilisation des ressources physiques sous-jacentes offertes par la machine hôte de nos chefs-d’œuvres compilés….

Un exemple concret

programmer en vue d’une utilisation multi-tâche n’est pas forcément simple. On peut être ainsi tenter d’y échapper jusqu’au jours où on tombe sur un cas tout simple qui va déboucher sur un magnifique plantage.

Comme le titre du billet l’indique, cela n’a rien de spécifique à MVVM, même si l’exemple sera programmé dans un mode de ce type (mais très édulcoré, ce n’est donc pas un exemple MVVM, pour cela se reporter à l’article complet que j’ai écrit sur le sujet).

Imaginons ainsi une vue présentant des données issues d’un ViewModel ou directement d’un Modèle (ce que MVVM autorise). Supposons ce dernier cas pour simplifier la “plomberie” de l’exemple. Ce Modèle simule une source de données un peu particulière puisqu’elle fonctionne en permanence et dépend de données externes qui arrivent un peu quand elles veulent. Cela n’a rien d’exotique, prenez un simple Chat, il y aura bien un Modèle fournissant les messages de l’interlocuteur fonctionnant en permanence et la fréquence d’arrivée des données (les messages) est aléatoire (quand l’interlocuteur a envi d’écrire).

Le modèle

Le Modèle de notre exemple sera bien plus simple encore : il fabrique des nombres aléatoires. Mais il les fabrique dans un thread séparé qui s’endort pour une durée aléatoire après chaque génération de nombre. Pour ce faire la classe RandomNumberPusher utilise un BackgroundWorker, classe du Framework permettant assez simplement de créer des tâches de fond (code source du projet à télécharger plus bas).

la Vue

Créons une vue (la MainPage sera suffisante pour cet exemple minimaliste) et attachons une instance de la classe RandomNumberPusher au DataContext de la grille principale. Ajoutons un TextBlock bindé (voir mon article sur le Binding Xaml) à la propriété Number. Et exécutons…

Le plantage…

A intervalle irrégulier l’instance du Modèle va générer en tâche de fond un nombre aléatoire et va déclencher la notification de changement d’état de la propriété Number (événement PropertyChanged). La Vue et son TextBlock étant bindés à cette dernière, la mise à jour du texte va se faire automatiquement. Oui mais… cela va planter. Pour une raison simple : seule le thread principal ayant créé les objets d’interface peut modifier ces derniers, or le générateur aléatoire fonctionne depuis un thread différent. On peut mettre le plantage encore plus en évidence si on tentait de modifier directement la propriété Text du TextBlock depuis la tâche de fond. Le modèle de programmation MVVM nous oblige à penser le code autrement (et c’est une bonne chose) et le plantage ne se verra pas forcément (le TextBlock ne changera pas de valeur). Mais à un moment donné il risque de s’afficher ceci :

Accès inter-thread non valide. Le couperet tombe.

Pour obtenir ce message de façon sûre il faut placer un point d’arrêt dans le thread de fond, s’arrêter dessus et repartir car le plus souvent le texte n’est tout simplement pas mis à jour. Comme je le disais plus haut, le plantage serait systématique si la tâche de fond mettait à jour directement le TextBlock ce que le montage de type MVVM évite. Le bug n’en est que plus sournois car il ne se passe rien, et rien, ce n’est pas grand chose ! Sur un écran un peu chargé il sera difficile de détecter le problème durant les tests.

Le mécanisme est simple :

Le thread de fond modifie la propriété Number, ce qui déclenche l’événement de changement de propriété (PropertyChanged), l’objet de binding est alors activé et tente de mettre à jour la valeur de la propriété Text du TextBlock. La plantage résulte du fait que la modification de ce dernier est effectué au travers d’un thread différent de celui gérant l’UI.

Le code xaml

   1:  <UserControl x:Class="SLMultiThread.MainPage"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   6:      xmlns:model="clr-namespace:SLMultiThread.Models"
   7:      mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
   8:      <UserControl.Resources>
   9:          <model:RandomNumberPusher x:Key="random" />
  10:      </UserControl.Resources>
  11:      <Grid x:Name="LayoutRoot" DataContext="{StaticResource random}">
  12:          <TextBlock Text="{Binding Number, Mode=OneWay}"/>
  13:      </Grid>
  14:  </UserControl>

On note : la déclaration du namespace de notre Modèle, la déclaration d’une instance du Modèle en ressource, la définition du DataContext du LayoutRoot depuis cette dernière et enfin, le TextBlock bindé à la propriété Number.

Le code C# de la classe RandomNumberPusher n’est pas en cause dans le mécanisme du plantage. Elle est programmée en répondant aux exigences d’une classe dite Thread-Safe. Donc pour l’instant oublions là.

Les solutions

Plusieurs aspects sont à prendre en compte lors d’une programmation multi-thread. Le plantage que nous venons de mettre en évidence n’est qu’un aspect des choses, limité uniquement à la spécificité de Xaml qui ne prend pas en charge la mise à jour de l’UI via un thread autre que le principal. On trouvait déjà ce problème sous Windows Forms d’ailleurs.

Concernant ce problème spécifique il y a plusieurs approches, mais au final il n’y a qu’un moyen : faire en sorte que la manipulation de l’UI ne passe que par son thread propre. Ponctuellement on peut utiliser, comme sous Windows Forms, des mécanismes d’invocation transférant le contrôle au thread de l’UI. Mais ce que nous cherchons est une solution globale et “industrialisable”. La plupart des auteurs qui a abordé le sujet arrive à la même conclusion, il faut passer par un mécanisme centralisé de dispatch. Cette solution peut se mettre en œuvre de différentes façons, voyons directement celle qui m’apparait la plus simple à appliquer.

Le SmartDispatcher et la classe de base

Le principe peut se schématiser comme suit :

Au début les choses sont, par force, identiques : la tâche de fond modifie la propriété (Number dans notre exemple) et déclenche PropertyChanged dans le Modèle. Mais au lieu que cet événement soit traité directement par le binding, nous intercalons le Dispatcher, c’est lui qui va vérifier que l’appel a lieu depuis le thread d’UI. Dans l’affirmative le traitement se poursuivra normalement, dans la négative, ce qui est le cas de notre exemple, il transfèrera la notification de changement de propriété au binding via une invocation appropriée. Comme le Dispatcher se trouve dans le thread de l’UI, et qu’il va faire en sorte de rendre “sien” les appels qui proviennent d’autres threads, le binding ne verra que des appels intra-thread UI et non inter-threads.

Cette solution fonctionne en deux étapes. Il y a le Dispatcher, mais il y a aussi le routage des notifications vers ce dispatcher. Modifier dans chaque classe l’appel à PropertyChanged pour qu’il pointe vers le Dispatcher serait fastidieux et source d’erreur. Le plus simple est donc de créer une classe de base dont hériterons les Modèles ou les ViewModels plus généralement (la connexion directe d’une Vue à un Modèle utilisée dans l’exemple est certes autorisé par MVVM mais ce n’est pas le “montage” le plus courant, le ViewModel étant souvent nécessaire pour la prise en charge des commandes, l’adaptation des données et la persistance de la Vue).

Le mécanisme du Binding et des patterns comme MVVM reposent sur un présupposé : que les classes reliées par binding à l’UI prennent en charge INotifyPropertyChanged. Il est donc raisonnable de mettre en œuvre une classe de base implémentant cette interface et s’occupant automatiquement de la liaison avec le Dispatcher.

Le seul problème que peut poser cette approche est que le ViewModel soit déjà dans l’obligation d’hériter d’une classe de base. Cela peut être le cas si vous utilisez un framework MVVM ou autre. C# ne gérant pas l’héritage multiple (bien heureusement), il faudra alors fusionner la solution du Dispatcher à la classe de base déjà utilisée. A moins que celle-ci ne prenne déjà en charge un mécanisme équivalent, mais dans ce cas je peux aller me coucher vous n’avez plus besoin de mes conseils ! :-)

Le code du Dispatcher

Parmi les implémentations existantes j’ai une préférence pour celle de Jeff Wilcox qui est un Senior Software Development Engineer chez Microsoft dans l’équipe de Silverlight… Autant se servir aux bonnes sources ! Son implémentation est sous licence Ms-PL (licence libre publique spécifique à Microsoft). J’avais dans mes tablettes un code assez proche mais pas aussi complet et on peut trouver des choses assez équivalentes dans le portage de Cairngorm sur Silverlight (Cairngorm sur CodePlex) , un framework MVC traduit depuis l’original de même nom conçu pour Flex de Adobe (un produit proche de Silverlight pour ceux qui ne connaissent pas).

   1:  using System;
   2:  using System.Windows;
   3:  using System.ComponentModel;
   4:  using System.Windows.Threading;
   5:   
   6:  namespace SLMultiThread.ThreadTools
   7:  {
   8:      // (c) Copyright Microsoft Corporation.
   9:      // This source is subject to the Microsoft Public License (Ms-PL).
  10:      // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  11:      // All other rights reserved.
  12:      
  13:      
  14:          /// <summary>
  15:          /// A smart dispatcher system for routing actions to the user interface
  16:          /// thread.
  17:          /// </summary>
  18:          public static class SmartDispatcher
  19:          {
  20:              /// <summary>
  21:              /// A single Dispatcher instance to marshall actions to the user
  22:              /// interface thread.
  23:              /// </summary>
  24:              private static Dispatcher instance;
  25:   
  26:              /// <summary>
  27:              /// Backing field for a value indicating whether this is a design-time
  28:              /// environment.
  29:              /// </summary>
  30:              private static bool? designer;
  31:   
  32:              /// <summary>
  33:              /// Requires an instance and attempts to find a Dispatcher if one has
  34:              /// not yet been set.
  35:              /// </summary>
  36:              private static void requireInstance()
  37:              {
  38:                  if (designer == null)
  39:                  {
  40:                      designer = DesignerProperties.IsInDesignTool;
  41:                  }
  42:   
  43:                  // Design-time is more of a no-op, won't be able to resolve the
  44:                  // dispatcher if it isn't already set in these situations.
  45:                  if (designer == true)
  46:                  {
  47:                      return;
  48:                  }
  49:   
  50:                  // Attempt to use the RootVisual of the plugin to retrieve a
  51:                  // dispatcher instance. This call will only succeed if the current
  52:                  // thread is the UI thread.
  53:                  try
  54:                  {
  55:                      instance = Application.Current.RootVisual.Dispatcher;
  56:                  }
  57:                  catch (Exception e)
  58:                  {
  59:                      throw new InvalidOperationException("The first time SmartDispatcher is used must be from a user interface thread. Consider having the application call Initialize, with or without an instance.", e);
  60:                  }
  61:   
  62:                  if (instance == null)
  63:                  {
  64:                      throw new InvalidOperationException("Unable to find a suitable Dispatcher instance.");
  65:                  }
  66:              }
  67:   
  68:              /// <summary>
  69:              /// Initializes the <see cref="SmartDispatcher"/> system, attempting to use the
  70:              /// RootVisual of the plugin to retrieve a Dispatcher instance.
  71:              /// </summary>
  72:              public static void Initialize()
  73:              {
  74:                  if (instance == null)
  75:                  {
  76:                      requireInstance();
  77:                  }
  78:              }
  79:   
  80:              /// <summary>
  81:              /// Initializes the <see cref="SmartDispatcher"/> system with the dispatcher
  82:              /// instance.
  83:              /// </summary>
  84:              /// <param name="dispatcher">The dispatcher instance.</param>
  85:              public static void Initialize(Dispatcher dispatcher)
  86:              {
  87:                  if (dispatcher == null)
  88:                  {
  89:                      throw new ArgumentNullException("dispatcher");
  90:                  }
  91:   
  92:                  instance = dispatcher;
  93:   
  94:                  if (designer == null)
  95:                  {
  96:                      designer = DesignerProperties.IsInDesignTool;
  97:                  }
  98:              }
  99:   
 100:              /// <summary>
 101:              ///
 102:              /// </summary>
 103:              /// <returns></returns>
 104:              public static bool CheckAccess()
 105:              {
 106:                  if (instance == null)
 107:                  {
 108:                      requireInstance();
 109:                  }
 110:   
 111:                  return instance.CheckAccess();
 112:              }
 113:   
 114:              /// <summary>
 115:              /// Executes the specified delegate asynchronously on the user interface
 116:              /// thread. If the current thread is the user interface thread, the
 117:              /// dispatcher if not used and the operation happens immediately.
 118:              /// </summary>
 119:              /// <param name="a">A delegate to a method that takes no arguments and
 120:              /// does not return a value, which is either pushed onto the Dispatcher
 121:              /// event queue or immediately run, depending on the current thread.</param>
 122:              public static void BeginInvoke(Action a)
 123:              {
 124:                  if (instance == null)
 125:                  {
 126:                      requireInstance();
 127:                  }
 128:   
 129:                  // If the current thread is the user interface thread, skip the
 130:                  // dispatcher and directly invoke the Action.
 131:                  if (instance.CheckAccess() || designer == true)
 132:                  {
 133:                      a();
 134:                  }
 135:                  else
 136:                  {
 137:                      instance.BeginInvoke(a);
 138:                  }
 139:              }
 140:          }
 141:  }

Le SmartDispatcher est une enveloppe intelligente autour de la classe Dispatcher du Framework de Silverlight. Cette dernière a été conçue justement pour gérer le code de mise à jour de l’UI depuis d’autres threads. SmartDispatcher “habille” le Dispatcher de base en s’assurant qu’une instance est disponible. En fait, appeler le Dispatcher de SL serait suffisant, il est conçu pour cela, mais cela n’est pas possible depuis une tâche de fond, il faut donc que l’instance existe déjà. Le SmartDispatcher permet de s’assurer de cette condition liminaire et fournit un point d’accès connu et centralisé à l’instance en question.

Bien que SmartDispatcher s’occupe d’obtenir l’instance du Dispatcher automatiquement il est préférable de l’initialiser très tôt dans le code de l’application, pour s’assurer que le SmartDispatcher est bien opérationnel et disponible avant tout événement impliquant l’interface. C’est pourquoi il est préférable d’ajouter dans App.xaml le code suivant dans le constructeur de l’objet App, juste avant l’appel à InitializeComponent() :

SmartDispatcher.Initialize(Deployment.Current.Dispatcher); 

Le SmartDispatcher est initialisé avec le Dispatcher fourni par le namespace Deployment qui s’occupe majoritairement du manifest et d’informations sur l’application en cours.

La classe de base

Il y a plusieurs façons de l’implémenter, mais puisque j’ai fait le choix de vous montrer l’implémentation de SmartDispatcher, autant utiliser la classe de base qui a été écrite par Wilcox pour fonctionner avec ce dernier, ça semble logique…

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel;
   4:   
   5:  namespace SLMultiThread.ThreadTools
   6:  {
   7:      // (c) Copyright Microsoft Corporation.
   8:      // This source is subject to the Microsoft Public License (Ms-PL).
   9:      // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  10:      // All other rights reserved.
  11:   
  12:          /// <summary>
  13:          /// A base class for data objects that implement the property changed
  14:          /// interface, offering data binding and change notifications.
  15:          /// </summary>
  16:          public class PropertyChangedBase : INotifyPropertyChanged
  17:          {
  18:              /// <summary>
  19:              /// A static set of argument instances, one per property name.
  20:              /// </summary>
  21:              private static readonly Dictionary<string, PropertyChangedEventArgs> argumentInstances = 
  22:                  new Dictionary<string, PropertyChangedEventArgs>();
  23:   
  24:              /// <summary>
  25:              /// The property changed event.
  26:              /// </summary>
  27:              public event PropertyChangedEventHandler PropertyChanged;
  28:   
  29:              /// <summary>
  30:              /// Notify any listeners that the property value has changed.
  31:              /// </summary>
  32:              /// <param name="propertyName">The property name.</param>
  33:              protected void NotifyPropertyChanged(string propertyName)
  34:              {
  35:                  if (string.IsNullOrEmpty(propertyName))
  36:                  {
  37:                      throw new ArgumentException("PropertyName cannot be empty or null.");
  38:                  }
  39:   
  40:                  var handler = PropertyChanged;
  41:                  if (handler == null) return;
  42:                  PropertyChangedEventArgs args;
  43:                  if (!argumentInstances.TryGetValue(propertyName, out args))
  44:                  {
  45:                      args = new PropertyChangedEventArgs(propertyName);
  46:                      argumentInstances[propertyName] = args;
  47:                  }
  48:   
  49:                  // Fire the change event. The smart dispatcher will directly
  50:                  // invoke the handler if this change happened on the UI thread,
  51:                  // otherwise it is sent to the proper dispatcher.
  52:                  SmartDispatcher.BeginInvoke(() => handler(this, args));
  53:              }
  54:          }
  55:   
  56:  }

Cette classe implémente INotifyPropertyChanged qui est à la base du mécanisme de propagation “automatique” des changements de valeurs utilisé par le binding et sur lequel repose des patterns comme MVVM.

La classe utilise un astuce pour améliorer les performances : elle créée un dictionnaire des PropertyChangedEventArgs de telle façon à ce que ce soit toujours la même instance d’arguments qui soit utilisée pour une propriété donnée. Ainsi, et surtout lorsque les valeurs changent très souvent, le Garbage Collector n’est pas submergé de petits objets à durée de vie ultra-courte. Il est certes optimisé pour cela, mais moins il y a de créations / destructions moins le GC a de travail et tout va plus vite.

Une autre optimisation est importante, elle concerne la détection de la nécessité ou non de transiter via le Dispatcher. Dans le cas où cela n’est pas nécessaire le message de notification suit son cours. Il est ainsi traité immédiatement alors que le passage via le Dispatcher créé un délai (même infime).

Le code exemple

Le projet complet (VS2008 SL3) est téléchargeable ici: SLMultiThread.zip (9,55 kb)

Les autres facettes du multi-thread

Ici nous n’avons fait qu’effleurer le sujet en abordant un problème très spécifique du multithreading sous Silverlight. Si vous regardez le code de plus près, notamment la classe RandomNumberPusher que nous n’avons pas étudiée dans ce billet, vous y trouverez des techniques simples propres au mutithreading comme l’utilisation d’un objet “locker” pour assurer le verrouillage des lectures et écritures entre les threads.

Le sujet est vaste et il serait possible d’en parler des heures. Je réserve cela pour un éventuel futur article, il faut bien en laisser un peu pour plus tard… Une raison de plus de …

Stay Tuned !