Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Présentation détaillée de MVVM Light Messenger
Cette série sur le Model-View-ViewModel (MVVM) et MVVM Light Shared Computer Toolkit a couvert beaucoup de terrain que j'ai commencé presque un an, à l'utilisation de conteneurs IOC dans les applications MVVM aux façons de gérer les accès inter-threads et le composant DispatcherHelper de MVVM Light. J'ai aussi parlé de commandant (avec le RelayCommand et EventToCommand), services de consultation, comme la Navigation et services de dialogue et discute brièvement de la composante de Messenger.
Le composant de messager est réellement tout à fait un puissant élément de MVVM Light Shared Computer Toolkit, qui souvent séduit les développeurs grâce à sa facilité d'utilisation, mais a aussi suscité une certaine controverse en raison des risques, qu'il peut créer si elle est mal utilisée. Ce composant mérite son propre article qui explique comment il fonctionne, quels sont les risques et les scénarios pour lesquels il fait le plus de sens.
Dans cet article, je vais discuter les principes généraux derrière la mise en œuvre du Messager et regardez pourquoi cette mise en œuvre est facile à utiliser que les approches plus traditionnelles. J'explorerai également comment cette approche peut avoir des répercussions mémoire si certaines précautions ne sont pas prises. Enfin, j'aborderai le MVVM Light Messenger lui-même plus en détail, en particulier certains des messages intégrés et leur utilisation.
Agrégation d'événements et Simplifications de Messenger
Systèmes comme le messager sont parfois nommées autobus événement ou agrégateurs de l'événement. Ces composants connectent à un émetteur et un récepteur (parfois appelée « éditeur » et « abonné », respectivement). Lorsque MVVM Light a été créé, plusieurs systèmes de messagerie requis du récepteur ou l'expéditeur mettre en œuvre des méthodes spécifiques. Par exemple, il aurait pu être une interface IReceiver qui a spécifié une méthode de réception, et pour vous inscrire avec le système de messagerie, un objet serait avez eu implémenter cette interface. Ce genre de contrainte était ennuyeux, car il restreint qui pourrait effectivement utiliser le système de messagerie. Par exemple, si vous utilisiez un assembly de fournisseur tiers, vous ne pouvait pas enregistrer une instance de cette bibliothèque avec le système de messagerie, car vous n'avaient pas accès au code et ne pouvait pas modifier la classe de fournisseur tiers pour implémenter IReceiver.
Le MVVM Light Messenger a été créé dans le but de rationaliser ce scénario avec un principe simple : N'importe quel objet peut être un récepteur ; n'importe quel objet peut être un expéditeur ; n'importe quel objet peut être un message.
Le vocabulaire, aussi, a été simplifié. Au lieu d'utiliser des mots tels que « agrégation d'événements », qui sont difficiles à définir, l'exposé porte sur la messagerie, qui est assez facile à comprendre. L'abonné devient le récepteur et l'éditeur devient l'expéditeur. Plutôt que des événements, il y a des messages. Ces simplifications dans la langue, ainsi que la mise en œuvre simplifiée, le rendent plus facile de démarrer avec le Messager et de comprendre comment il fonctionne.
Par exemple, considérez le code dans Figure 1. Comme vous pouvez le voir, le Messager de lumière MVVM est utilisé dans deux objets distincts. L'objet de l'enregistrement envoie un message à toutes les instances d'utilisateur inscrit. Ce genre de scénario peut être implémenté de plusieurs façons, et le Messager ne peut-être pas toujours la meilleure solution. Mais, en fonction de votre architecture, ça pourrait être un très bon outil pour implémenter cette fonctionnalité, surtout si l'expéditeur et le récepteur sont en parties de la demande qui devrait rester découplé. Remarquez comment l'instance d'enregistrement n'envoie pas aux instances utilisateur inscrit explicitement. Au lieu de cela, elle diffuse le message par le Messager. N'importe quelle instance peut s'inscrire pour ce type de message et être notifiée lors de l'envoi. Dans cet exemple, le message envoyé est une instance de RegistrationInfo. Cependant, n'importe quel type de message peut être envoyé, de valeurs simples (int, bool, etc.) aux objets message dédié. Plus tard, je vais discuter de l'aide de messages et en revue quelques-uns des types de message intégré dans MVVM Light.
Figure 1 envoi et la réception d'un Message
public class Registration
{
public void SendUpdate()
{
var info = new RegistrationInfo
{
// ...
Some properties
};
Messenger.Default.Send(info);
}
}
public class RegisteredUser
{
public RegisteredUser()
{
Messenger.Default.Register<RegistrationInfo>(
this,
HandleRegistrationInfo);
}
private void HandleRegistrationInfo(RegistrationInfo info)
{
// Update registered user info
}
}
public class RegistrationInfo
{
// ...
Some properties
}
Le code dans Figure 1 montre que l'inscription d'un type de message (RegistrationInfo) est fait par un délégué (HandleRegistrationInfo). Il s'agit d'un mécanisme commun dans le Microsoft .NET Framework. Par exemple, enregistrer un gestionnaire d'événements en c# est également fait en passant un délégué à l'événement, une méthode nommée ou une expression lambda anonyme. De même, vous pouvez utiliser méthodes nommées ou lambdas anonymes pour enregistrer un récepteur avec le Messager, comme le montre Figure 2.
La figure 2, inscription auprès de méthodes nommées ou Lambdas
public UserControl()
{
InitializeComponent();
// Registering with named methods ----
Loaded += Figure2ControlLoaded;
Messenger.Default.Register<AnyMessage>(
this,
HandleAnyMessage);
// Registering with anonymous lambdas ----
Loaded += (s, e) =>
{
// Do something
};
Messenger.Default.Register<AnyMessage>(
this,
message =>
{
// Do something
});
}
private void HandleAnyMessage(AnyMessage message)
{
// Do something
}
private void Figure2ControlLoaded (object sender, RoutedEventArgs e)
{
// Do something
}
Accès inter-threads
Une chose que ne fait pas le messager est montre sur quel thread, un message est envoyé. Si vous avez lu mon article précédent, « Multithreading et Dispatching dans MVVM Applications » (bit.ly/1mgZ0Cb), vous savez que certaines précautions doivent être prises lorsqu'un objet qui s'exécute sur un thread tente d'accéder à un objet appartenant à un autre thread. Ce problème survient souvent entre un thread d'arrière-plan et un contrôle détenu par le thread d'interface utilisateur. Dans l'article précédent, vous avez vu comment le MVVM Light DispatcherHelper peut être utilisé pour « expédition » de l'opération sur le thread d'interface utilisateur et éviter l'exception accès inter-threads.
Certains agrégateurs d'événements vous permettent d'envoyer automatiquement des messages envoyés vers le thread d'interface utilisateur. Le MVVM Light Messenger ne fait jamais que, toutefois, en raison de la volonté de simplifier l'API Messenger. Ajout d'une option d'envoyer automatiquement les messages vers le thread d'interface utilisateur voudrais ajouter plus de paramètres pour les méthodes d'enregistrement. En outre, cela rendrait l'envoi moins explicite et peut-être plus difficile pour les développeurs moins expérimentés à comprendre ce qui se passe en coulisses.
Au lieu de cela, vous devriez explicitement d'expédition des messages vers le thread d'interface utilisateur si nécessaire. La meilleure façon de faire qui consiste à utiliser le MVVM Light DispatcherHelper. Comme indiqué dans l'article précédent, la méthode CheckBeginInvokeOnUI dépêchera l'opération seulement si nécessaire. Si le messager est déjà en cours d'exécution sur le thread d'interface utilisateur, le message peut être distribué immédiatement sans expédition :
public void RunOnBackgroundThread()
{
// Do some background operation
DispatcherHelper.CheckBeginInvokeOnUI(
() =>
{
Messenger.Default.Send(new ConfirmationMessage());
});
}
Manipulation de la mémoire
Tout système qui permet aux objets de communiquer sans connaître les visages entre eux la lourde tâche d'avoir à enregistrer une référence aux destinataires du message. Par exemple, imaginons que le système de gestion d'événements .NET peuvent créer des références fortes entre un objet qui déclenche un événement et un objet qui s'abonne à l'événement. Le code dans Figure 3 crée un lien fort entre _first et _La. Cela veut dire que si la méthode de nettoyage est appelée, et _La a la valeur null, le garbage collector ne peut pas supprimer de la mémoire, parce que _first conserve une référence à celui-ci. Le garbage collector s'appuie sur le comptage des références à un objet de savoir si elle peut être supprimée de la mémoire, et cela ne se produit pour la seconde instance, donc une fuite de mémoire est créée. Au fil du temps, cela peut causer beaucoup de problèmes ; l'application pourrait ralentir considérablement, et par la suite, il peut même en panne.
Référence Strong figure 3 entre Instances
public class Setup
{
private First _first = new First();
private Second _second = new Second();
public void InitializeObjects()
{
_first.AddRelationTo(_second);
}
public void Cleanup()
{
_second = null;
// Even though this is set to null, the Second instance is
// still kept in memory because the reference count isn't
// zero (there's still a reference in _first).
}
}
public class First
{
private object _another;
public void AddRelationTo(object another)
{
_another = another;
}
}
public class Second
{
}
Pour atténuer ce, les développeurs .NET est venu avec l'objet WeakReference. Cette classe permet une référence à un objet à être rangés de manière à « faible ». Si toutes les autres références à cet objet sont définies sur null, le garbage collector peut encore percevoir l'objet, même s'il existe une WeakReference à l'utiliser. C'est très pratique, et lorsqu'il est utilisé avec sagesse, il permet de soulager le problème de fuites de mémoire, même si cela ne résout toujours pas toutes les questions. Pour illustrer cela, Figure 4 montre un système de communication simple, dans lequel l'objet SimpleMessenger stocke la référence vers le récepteur dans une WeakReference. Notez le contrôle à la propriété IsAlive avant que le message est traité. Si le récepteur a été supprimé et la mémoire nettoyée avant, la propriété IsAlive peuvent être faussée. Il s'agit d'un signe que le WeakReference n'est pas plus valable et devrait être supprimé.
Figure 4 utilisation d'Instances de WeakReference
public class SuperSimpleMessenger
{
private readonly List<WeakReference> _receivers
= new List<WeakReference>();
public void Register(IReceiver receiver)
{
_receivers.Add(new WeakReference(receiver));
}
public void Send(object message)
{
// Locking the receivers to avoid multithreaded issues.
lock (_receivers)
{
var toRemove = new List<WeakReference>();
foreach (var reference in _receivers.ToList())
{
if (reference.IsAlive)
{
((IReceiver)reference.Target).Receive(message);
}
else
{
toRemove.Add(reference);
}
}
// Prune dead references.
// Do this in another loop to avoid an exception
// when modifying a collection currently iterated.
foreach (var dead in toRemove)
{
_receivers.Remove(dead);
}
}
}
}
Le Messager de lumière MVVM repose sur à peu près le même principe, même s'il est, bien sûr, un peu plus complex ! Notamment, parce que le Messager ne nécessite pas le récepteur implémenter une interface donnée, il a besoin stocker une référence à la méthode (le rappel) qui sera utilisée pour transmettre le message. Windows Presentation Foundation (WPF) et l'exécution de Windows, ce n'est pas un problème. Dans Silverlight et Windows Phone, cependant, le cadre est plus sécurisé et les API d'empêchent certaines opérations ne se produise. Une de ces restrictions frappe le système de messager dans certains cas.
Pour comprendre cela, vous devez savoir quel type de méthodes peut être enregistré pour gérer les messages. Pour résumer, une méthode de réception peut être statique, ce qui n'est jamais un problème ; ou il peut être une méthode d'instance, auquel cas vous la différence entre les publics, internes et privés. Dans de nombreux cas, une méthode de récepteur est une expression lambda anonyme, ce qui équivaut à une méthode privée.
Lorsqu'une méthode est statique ou public, il n'y a aucun risque de créer une fuite de mémoire. Lorsque la méthode de gestion est interne ou privé (ou une anonyme lambda), il peut y avoir un risque dans Silverlight et Windows Phone. Malheureusement, dans ces cas n'a aucun moyen pour le Messager d'utiliser une WeakReference. Encore une fois, ce n'est pas un problème dans WPF ou l'exécution de Windows. Figure 5 résume ces informations.
Figure 5 risque de fuite de mémoire sans annulation d'inscription
Visibility | WPF | Silverlight | Windows Phone 8 | Windows Runtime |
Champ statique | pas de risque | pas de risque | pas de risque | pas de risque |
Publiques | pas de risque | pas de risque | pas de risque | pas de risque |
Interne | pas de risque | risque | risque | pas de risque |
Privées | pas de risque | risque | risque | pas de risque |
Lambda anonyme | pas de risque | risque | risque | pas de risque |
Il est important de noter que, même s'il existe un risque, comme indiqué dans Figure 5, omettant d'annuler l'inscription n'est pas toujours créer une fuite de mémoire. Cela dit, pour ne s'assurer qu'aucun mémoire fuite est provoquée, il est conseillé d'annuler l'inscription explicitement les récepteurs de la messagerie, quand ils ne sont pas plus nécessaire. Cela peut se faire à l'aide de la méthode Unregister. Notez qu'il y a plusieurs surcharges de Unregister. Un récepteur peut être complètement non enregistré de la messagerie, ou sélectionnez Annuler l'inscription qu'une méthode donnée, mais de garder d'autres actifs.
Autres risques lorsque vous utilisez le Messager
Comme j'ai indiqué, bien que le Messager de lumière MVVM est un composant très puissant et polyvalent, il est important de garder à l'esprit qu'il ya des risques à l'utiliser. J'ai déjà mentionné les fuites de mémoire potentiel dans Silverlight et Windows Phone. Un autre risque est moins technique : En utilisant le Messager découple les objets tellement qu'il peut être difficile de comprendre exactement ce qui se passe lorsqu'un message est envoyé et reçu. Pour un développeur, moins expérimentés, qui n'a jamais utilisé un bus d'événements avant, il peut être difficile de suivre le flux des opérations. Par exemple, si vous êtes pas dans l'appel de la méthode, et cette méthode appelle la méthode Messenger.Send, le débit de la mise au point est perdu sauf si vous savez pour rechercher la méthode correspondante de la Messenger.Receive et à y placer un point d'arrêt. Cela dit, les opérations de messager sont synchrones, et si vous comprenez comment le Messager fonctionne, il est toujours possible de déboguer ce flux.
J'ai tendance à utiliser la messagerie comme « dernier recours », lorsque les techniques de programmation plus conventionnels sont soit impossible ou causer trop de dépendances entre les parties de la demande, je veux garder comme découplés que possible. Parfois, cependant, il est préférable d'utiliser d'autres outils, comme un conteneur IOC et services afin d'atteindre des résultats similaires d'une manière plus explicite. J'ai parlé de services CIO et vue dans le premier article de cette série (bit.ly/1m9HTBX).
Un ou plusieurs messagers
Un des avantages des systèmes de messagerie tels que le Messager de lumière MVVM est qu'ils peuvent être utilisés même à travers les assemblys — dans les scénarios de plug-in, par exemple. Il s'agit d'une architecture commune pour la construction de grandes applications, en particulier dans WPF. Mais un système de plug-in peut également être utile pour des applications plus petites, d'ajouter facilement de nouvelles fonctionnalités sans avoir à recompiler la partie principale, par exemple. Dès qu'une DLL est chargée dans l'AppDomain de l'application, les classes qu'il contient peuvent utiliser le MVVM Light Messenger pour communiquer avec n'importe quel composant dans l'application même. C'est très puissant, surtout quand l'application principale ne sait pas combien de sous-composants sont chargés, qui est généralement le cas dans un plug-in basé sur demande.
En général, une application requiert uniquement une seule instance de Messenger pour couvrir toutes les communications. L'instance de static stocké dans la propriété Messenger.Default est probablement tout ce dont vous avez besoin. Toutefois, vous pouvez créer de nouvelles instances de Messenger si nécessaire. Dans ce cas, chaque Messager agit comme un canal de communication distincte. Cela peut être utile si vous voulez être sûr qu'un objet donné jamais reçoit un message ne pas prévu pour cela. Dans le code de Figure 6, par exemple, les deux classes s'inscrire pour le même type de message. Lorsque le message est reçu, les deux instances doivent effectuer des vérifications pour voir ce que fait le message.
Figure 6 à l'aide de la messagerie par défaut et vérification de l'expéditeur
public class FirstViewModel
{
public FirstViewModel()
{
Messenger.Default.Register<NotificationMessage>(
this,
message =>
{
if (message.Sender is MainViewModel)
{
// This message is for me.
}
});
}
}
public class SecondViewModel
{
public SecondViewModel()
{
Messenger.Default.Register<NotificationMessage>(
this,
message =>
{
if (message.Sender is SettingsViewModel)
{
// This message is for me
}
});
}
}
La figure 7 montre une implémentation avec une instance privée de Messenger. Dans ce cas, le SecondViewModel ne recevra jamais le message, parce qu'il s'abonne à une autre instance de Messenger et à l'écoute sur un canal différent.
Figure 7 utilisation d'un messager privé
public class MainViewModel
{
private Messenger _privateMessenger;
public MainViewModel()
{
_privateMessenger = new Messenger();
SimpleIoc.Default.Register(() => _privateMessenger,
"PrivateMessenger");
}
public void Update()
{
_privateMessenger.Send(new NotificationMessage("DoSomething"));
}
}
public class FirstViewModel
{
public FirstViewModel()
{
var messenger
= SimpleIoc.Default.GetInstance<Messenger>("PrivateMessenger");
messenger.Register<NotificationMessage>(
this,
message =>
{
// This message is for me.
});
}
}
Une autre façon d'éviter l'envoi d'un message donné à un récepteur particulier est d'utiliser des jetons, comme le montre Figure 8. Il s'agit d'une sorte de contrat entre un expéditeur et un destinataire. En général, un jeton est un identificateur unique comme un GUID, mais ça pourrait être n'importe quel objet. Si un expéditeur et un destinataire utilisent tous deux le même jeton, un canal de communication privée s'ouvre entre les deux objets. Dans ce scénario, la SecondViewModel qui n'a pas utilisé le jeton jamais annoncera qu'un message est envoyé. Le principal avantage est que le récepteur n'a pas besoin d'écrire une logique pour s'assurer que le message était vraiment destiné pour elle. Au lieu de cela, le Messager filtre les messages basés sur le jeton.
Figure 8 différents canaux de Communication avec jetons
public class MainViewModel
{
public static readonly Guid Token = Guid.NewGuid();
public void Update()
{
Messenger.Default.Send(new NotificationMessage("DoSomething"),
Token);
}
}
public class FirstViewModel
{
public FirstViewModel()
{
Messenger.Default.Register<NotificationMessage>(
this,
MainViewModel.Token,
message =>
{
// This message is for me.
});
}
}
À l'aide de Messages
Les jetons sont une belle façon de filtrer les messages, mais cela ne change pas le fait qu'un message doit transportent certains contexte pour être comprise. Par exemple, vous pouvez utiliser l'envoyer et recevoir des méthodes avec booléenne contenu, comme indiqué dans Figure 9. Mais si plusieurs émetteurs envoient des messages booléennes, comment un récepteur censé savoir à qui le message était destiné et quoi faire avec elle ? C'est pourquoi il est préférable d'utiliser un type de message dédiée pour clarifier le contexte.
La figure 9 en utilisant un Type de Message pour définir le contexte
public class Sender
{
public void SendBoolean()
{
Messenger.Default.Send(true);
}
public void SendNotification()
{
Messenger.Default.Send(
new NotificationMessage<bool>(true, Notifications.PlayPause));
}
}
public class Receiver
{
public Receiver()
{
Messenger.Default.Register<bool>(
this,
b =>
{
// Not quite sure what to do with this boolean.
});
Messenger.Default.Register<NotificationMessage<bool>>(
this,
message =>
{
if (message.Notification == Notifications.PlayPause)
{
// Do something with message.Content.
Debug.WriteLine(message.Notification + ":" +
message.Content);
}
});
}
}
Figure 9 montre aussi un type de message spécifique utilisé. NotificationMessage < T > est un des types plus couramment utilisés de message intégré dans le MVVM Light Shared Computer Toolkit, et il permet de tout contenu (dans ce cas, une valeur booléenne) envoyé avec une chaîne de notification. En général, la notification est une chaîne unique définie dans une classe statique appelée Notifications. Cela permet d'envoyer des instructions ainsi que le message.
Bien sûr, il est également possible de dériver de NotificationMessage < T > ; d'utiliser un type de message intégré différent ; ou à mettre en œuvre vos propres types de message. Le MVVM Light Shared Computer Toolkit contient une classe MessageBase qui peut être tirée à cette fin, même si ce n'est absolument pas obligatoire pour l'utiliser dans votre code.
Un autre type de message intégré est le PropertyChangedMessage < T >. Ceci est particulièrement utile en ce qui concerne l'Observableobjet et la classe ViewModelBase qui est généralement utilisée comme classe de base pour les objets impliqués dans la liaison des opérations. Ces classes sont des implémentations de l'interface INotifyPropertyChanged, qui est crucial dans les applications MVVM avec liaison de données. Par exemple, dans le code dans Figure 10, le BankAccountViewModel définit une propriété observable nommée Balance. Lorsque cette propriété change, la méthode RaisePropertyChanged prend un paramètre booléen qui provoque la classe ViewModelBase de « diffuser » un PropertyChangedMessage avec des informations sur cette propriété, comme son nom, l'ancienne valeur et la nouvelle valeur. Un autre objet peut s'abonner à ce type de message et réagir en conséquence.
Figure 10 envoi un PropertyChangedMessage
public class BankViewModel : ViewModelBase
{
public const string BalancePropertyName = "Balance";
private double _balance;
public double Balance
{
get
{
return _balance;
}
set
{
if (Math.Abs(_balance - value) < 0.001)
{
return;
}
var oldValue = _balance;
_balance = value;
RaisePropertyChanged(BalancePropertyName, oldValue, value, true);
}
}
}
public class Receiver
{
public Receiver()
{
Messenger.Default.Register<PropertyChangedMessage<double>>(
this,
message =>
{
if (message.PropertyName == BankViewModel.BalancePropertyName)
{
Debug.WriteLine(
message.OldValue + " --> " + message.NewValue);
}
});
}
}
Il y a des autres messages intégrés dans MVVM Light qui sont utiles dans divers scénarios. En outre, l'infrastructure pour créer vos propres messages personnalisés est disponible. Pour l'essentiel, l'idée est de faciliter la vie des récepteurs en fournissant un contexte suffisant pour qu'ils sachent quoi faire avec le contenu du message.
Synthèse
Le messager a prouvé tout à fait utile dans de nombreux scénarios, ce qui seraient difficiles à mettre en œuvre sans une solution de messagerie totalement découplée. Toutefois, il est un outil avancé et doit être utilisée avec précaution pour éviter de créer le code source de confusion qui pourrait être difficile de déboguer et de maintenir par la suite.
Cet article vient compléter la présentation des composants de MVVM Light Shared Computer Toolkit . C'est une période excitante pour les développeurs .NET, avec la possibilité d'utiliser les mêmes outils et techniques sur de multiples plateformes basées sur XAML. Avec MVVM Light, vous pouvez partager du code entre WPF, l'exécution de Windows, Windows Phone, Silverlight — et même les plateformes Novell pour Android et iOS. J'espère que vous avez trouvé cette série d'articles utiles pour comprendre comment MVVM Light peut vous aider à développer vos applications efficacement, tout en la rendant facile à concevoir, tester et maintenir ces applications.
Laurent Bugnion est directeur principal de IdentityMine Inc., un partenaire de Microsoft, travaillant avec des technologies telles que la Windows Presentation Foundation, Silverlight, Pixelsense, Kinect, Windows 8, Windows Phone et UX. Il est basé à Zurich, en Suisse. Il est également MVP Microsoft et directeur régional Microsoft.
Merci à l'expert technique Microsoft suivant d'avoir relu cet article : Jeffrey Ferman
Jeffrey Ferman sert actuellement comme responsable de programme Visual Studio. Depuis plus de quatre ans maintenant, Jeff s'est concentrée sur XAML d'outillage en Visual Studio et Blend. Il aime créer des applications métier d'et expérimente des pratiques et des habitudes de conception différente. Il a une passion pour l'extensibilité et aime travailler avec des clients pour construire des expériences au moment du design pour les contrôles.