Comment gérer l'état initial dans une architecture pilotée par les événements?

33

Dans une architecture pilotée par les événements, chaque composant agit uniquement lorsqu'un événement est envoyé via le système.

Imaginez une voiture hypothétique avec une pédale de frein et un feu stop.

  • Les tours de lumière de frein sur quand il reçoit un brake_on événement, et au large quand il reçoit un brake_off événement.
  • La pédale de frein envoie un événement brake_on lorsqu'elle est enfoncée et un événement brake_off lorsqu'elle est relâchée.

C’est très bien jusqu’à ce que la voiture soit allumée avec la pédale de frein déjà enfoncée . Puisque le feu stop n'a jamais reçu d' événement de freinage , il restera éteint - ce qui est clairement une situation indésirable. Allumer le feu stop par défaut ne fait que renverser la situation.

Que pourrait-on faire pour résoudre ce "problème d'état initial"?

EDIT: Merci pour toutes les réponses. Ma question ne concernait pas une voiture réelle. Dans les voitures, ils ont résolu ce problème en envoyant en permanence l'état. Par conséquent, il n'y a pas de problème de démarrage dans ce domaine. Dans mon domaine logiciel, cette solution utiliserait de nombreux cycles de calcul inutiles.

EDIT 2: En plus de la réponse de @ gbjbaanb , je choisis un système dans lequel:

  • la pédale de frein hypothétique, après l’initialisation, envoie un événement avec son état, et
  • le feu de freinage hypothétique, après l'initialisation, envoie un événement demandant un événement d'état à la pédale de frein.

Avec cette solution, il n'y a pas de dépendances entre les composants, pas de conditions de concurrence, pas de files de messages à vider et pas de composants "maîtres".

Frank Kusters
la source
2
La première chose qui me vient à l’esprit est de générer un événement «synthétique» (appelez-le initialize) contenant les données de capteur nécessaires.
msw
La pédale ne devrait-elle pas envoyer un événement brake_pedal_on et le frein lui-même envoyer l'événement brake_on? Je ne voudrais pas que mon feu de freinage s'allume si le frein ne fonctionnait pas.
bdsl
3
Ai-je mentionné qu'il s'agissait d'un exemple hypothétique? :-) C'est très simplifié de garder la question courte et précise.
Frank Kusters

Réponses:

32

Il y a plusieurs façons de le faire, mais je préfère garder un système basé sur les messages aussi découplé que possible. Cela signifie que le système dans son ensemble ne peut lire l'état d'aucun composant, ni aucun composant lire l'état d'un autre (car cela crée des liens spaghetti de dépendances).

Ainsi, alors que le système en fonctionnement se débrouillera tout seul, nous avons besoin d'un moyen d'indiquer à chaque composant de se lancer lui-même, et nous avons déjà une telle chose dans l'enregistrement de composant, c'est-à-dire qu'au démarrage, le système principal doit informer chaque composant qu'il est maintenant enregistré (ou demandera à chaque composant de renvoyer ses détails pour pouvoir être enregistré). C'est à ce stade que le composant peut effectuer ses tâches de démarrage et envoyer des messages comme il le ferait en fonctionnement normal.

Ainsi, lorsque le contact sera démarré, la pédale de frein recevrait un message d’enregistrement / contrôle de la part de la direction du véhicule et renverrait non seulement le message "Je suis ici et travaille", mais elle vérifiait alors son propre état et envoyait le message. messages pour cet état (par exemple, un message de pédale enfoncée).

Le problème devient alors un problème de dépendance au démarrage, car si le feu de freinage n’est pas encore enregistré, il ne recevra pas le message. Ce problème est facilement résolu en mettant tous ces messages en file d’attente jusqu’à ce que le système principal ait terminé sa routine de démarrage, d’enregistrement et de vérification. .

Le principal avantage est qu'aucun code spécial n'est requis pour gérer l'initialisation, sauf que vous devez déjà écrire (ok, si l'envoi des messages pour les événements de pédale de frein se trouve dans un gestionnaire de pédale de frein, vous devrez également l'indiquer lors de l'initialisation. , mais ce n’est généralement pas un problème si vous n’avez pas écrit ce code étroitement lié à la logique du gestionnaire) et aucune interaction entre les composants, à l’exception de ceux qu’ils s’envoient déjà normalement. Les architectures de passage de messages sont très bonnes pour cette raison!

gbjbaanb
la source
1
J'aime votre réponse, car elle garde tous les composants découplés - c'était la raison la plus importante de choisir cette architecture. Cependant, à l'heure actuelle, il n'y a pas de véritable composant "maître" qui décide que le système est dans un état "initialisé" - tout commence à fonctionner. Avec le problème dans ma question à la suite. Une fois que le maître a décidé que le système est en cours d’exécution, il peut envoyer un événement «Système initialisé» à tous les composants, après quoi chaque composant commence à diffuser son état. Problème résolu. Merci! (Il ne me reste maintenant plus que le problème: comment décider si le système est initialisé ...)
Frank Kusters
Pourquoi ne pas laisser le répartiteur de mise à jour de statut garder trace de la dernière mise à jour reçue de chaque objet et, chaque fois qu’une nouvelle demande d’abonnement est reçue, lui envoyer les mises à jour les plus récentes qu’il a reçues des sources d’événements enregistrées?
Supercat
Dans ce cas, vous devez également savoir quand les événements expirent. Tous les événements ne doivent pas être conservés indéfiniment pour les nouveaux composants susceptibles de s’inscrire.
Frank Kusters
@spaceknarf et bien, dans le cas où "tout commence à courir", vous ne pouvez pas créer de dépendance dans les composants afin que la pédale démarre après la lumière, vous devrez simplement les démarrer dans cet ordre, bien que j'imagine que quelque chose les démarre, alors exécutez les dans le "bon" ordre (par exemple, les scripts d'initialisation de démarrage linux avant systemd où le service à démarrer en premier s'appelle 1.xxx et le second s'appelle 2.xxx, etc.).
gbjbaanb
Les scripts avec une telle commande sont fragiles. Il contient beaucoup de dépendances implicites. Au lieu de cela, je pensais que si vous avez un composant 'maître', qui a une liste de composants configurés de manière statique devant être exécuté (comme mentionné par @Lie Ryan), il peut alors diffuser un événement 'prêt' une fois que tous ces composants sont chargés. En réponse à cela, tous les composants diffusent leur état initial.
Frank Kusters
4

Vous pouvez avoir un événement initialize qui définit les états de manière appropriée lors du chargement / démarrage. Cela peut être souhaitable pour des systèmes simples ou des programmes n'incluant pas plusieurs composants matériels, mais pour des systèmes plus complexes comportant plusieurs composants physiques car vous courez le même risque que de ne pas initialiser du tout - si un événement de "freinage" est manqué ou perdu le long de votre communication système (par exemple, un système basé sur CAN), vous pouvez par inadvertance régler votre système en arrière comme si vous l’aviez démarré avec le frein enfoncé. Plus vous aurez de contrôleurs, comme avec une voiture, plus il est probable que quelque chose manque.

Pour tenir compte de cela, vous pouvez demander à la logique "Frein activé" d'envoyer à plusieurs reprises des événements "Frein activé". Peut-être toutes les 1/100 de seconde ou quelque chose comme ça. Votre code contenant le cerveau peut écouter ces événements et déclencher un «frein» pendant qu'il les reçoit. Après 1 / 10sec de ne pas recevoir les signaux "freinage sur", il déclenche un événement interne "brake_off".

Différents événements auront des exigences temporelles considérablement différentes. Dans une voiture, votre feu de freinage doit être beaucoup plus rapide que votre voyant de carburant de contrôle (où un délai de plusieurs secondes est probablement acceptable) ou d’autres systèmes moins importants.

La complexité de votre système physique dictera laquelle de ces approches est la plus appropriée. Étant donné que votre exemple est un véhicule, vous voudriez probablement quelque chose de similaire à ce dernier.

Quoi qu'il en soit, avec un système physique, vous ne voulez PAS compter sur un seul événement reçu / traité correctement. Les microcontrôleurs connectés sur un système en réseau ont souvent un délai d'expiration «Je suis en vie» pour cette raison.

Enderland
la source
dans un système physique, vous utiliseriez une logique binaire pour faire fonctionner un fil: HIGH est pressé sur les freins et LOW est freiné non pas
maniaque du cliquet
@ratchetfreak, il y a beaucoup de possibilités pour ce genre de chose. Peut-être qu'un commutateur peut gérer cela. Il y a beaucoup d'autres événements système qui ne sont pas gérés simplement.
Enderland
1

Dans ce cas, je ne modéliserais pas le frein en tant que simple marche / arrêt. J'enverrais plutôt des événements de "pression de freinage" Par exemple, une pression de 0 indiquerait un arrêt et une pression de 100 serait complètement abaissée. Le système (nœud) envoie en permanence des événements de pression de rupture (à un certain intervalle) au (x) contrôleur (s) en fonction des besoins.

Lors du démarrage du système, les événements de pression commençaient à se déclencher jusqu'à sa mise hors tension.

Jon Raynor
la source
1

Si votre seul moyen de transmettre des informations d'état est à travers des événements, alors vous êtes en difficulté. Au lieu de cela, vous devez être capable de:

  1. interroger l'état actuel de la pédale de frein, et
  2. enregistrez-vous pour les événements "d'état changé" à partir de la pédale de frein.

Le feu stop peut être vu comme un observateur de la pédale de frein. En d'autres termes, la pédale de frein ne sait rien du feu stop et peut fonctionner sans lui. (Cela signifie que toute idée de pédale de frein qui envoie de manière proactive un événement "d'état initial" au feu stop est mal conçue.)

Lors de l’instanciation du système, le feu stop s’enregistre sur la pédale de frein pour recevoir des notifications de freinage, lit également l’état actuel de la pédale de frein et s’allume ou s’éteint.

Ensuite, les notifications de freinage peuvent être implémentées de l’une des trois manières suivantes:

  1. comme événements "paramètres de la pédale de freinage modifiés" sans paramètre
  2. comme une paire de "pédale de freinage est maintenant enfoncée" et d'événements "la pédale de frein est maintenant relâchée"
  3. en tant qu'événement "nouvel état de pédale de freinage" avec un paramètre "enfoncé" ou "relâché".

Je préfère la première approche, ce qui signifie qu'à la réception de la notification, le feu stop fera simplement ce qu'il sait déjà faire: lisez l'état actuel de la pédale de frein et allumez ou éteignez lui-même.

Mike Nakis
la source
0

Dans un système événementiel (que j'utilise et que j'aime actuellement), je trouve important de garder les choses aussi découplées que possible. Alors, avec cette idée en tête, approfondissons la question.

Il est important d'avoir un état par défaut. Votre voyant de frein prendrait l'état par défaut «éteint» et votre pédale de frein prendrait l'état par défaut «haut». Tout changement par la suite serait un événement.

Maintenant pour répondre à votre question. Imaginez que votre pédale de frein soit initialisée et enfoncée, l'événement se déclenche, mais il n'y a pas encore de feu de freinage pour recevoir l'événement. J'ai trouvé plus facile de séparer la création des objets (où les écouteurs d'événements seraient initialisés) en une étape distincte avant d' initialiser une logique. Cela évitera toutes les conditions de compétition, comme vous l'avez décrit.

Je trouve également délicat d’utiliser deux événements différents pour ce qui est effectivement la même chose . brake_offet brake_onpourrait être simplifié e_brakeavec un paramètre bool on. Vous pouvez ainsi simplifier vos événements en ajoutant des données de support.

Le poisson bleu
la source
0

Ce dont vous avez besoin est un événement de diffusion et des boîtes de réception de messages. Une émission est un message qui est publié à un nombre non spécifié d'auditeurs. Un composant peut s'abonner à des événements de diffusion pour ne recevoir que les événements qui l'intéressent. Cela permet le découplage, car l'expéditeur n'a pas besoin de savoir qui sont les destinataires. La table d'abonnement doit être configurée de manière statique lors de l'installation du composant (au lieu de lors de son initialisation). La boîte de réception est une partie du routeur de messages qui joue le rôle de tampon pour conserver les messages lorsque le composant de destination est hors ligne.

L'utilisation des factures pose un problème, la taille de la boîte de réception. Vous ne voulez pas que le système doive conserver un nombre croissant de messages pour des composants qui ne seront plus jamais en ligne. Ceci est particulièrement important avec les systèmes embarqués soumis à des contraintes de mémoire strictes. Pour dépasser la taille limite de la boîte de réception, tous les messages diffusés doivent suivre quelques règles. Les règles sont:

  1. chaque événement de diffusion nécessite un nom
  2. à un moment donné, l'expéditeur d'un événement de diffusion ne peut avoir qu'une seule diffusion active portant le nom spécifié
  3. l'effet causé par l'événement doit être idempotent

Le nom de la diffusion doit être déclaré pendant l’installation du composant. Si un composant envoie une deuxième diffusion avec le même nom avant que le récepteur ne traite la précédente, la nouvelle diffusion remplace la précédente. Vous pouvez maintenant avoir une limite statique de taille de la boîte de réception, qui peut être garantie de ne jamais dépasser une certaine taille et peut être précalculée en fonction des tables d'abonnement.

Enfin, vous avez également besoin d'une archive de diffusion. L'archive de diffusion est une table contenant le dernier événement de chaque nom de diffusion. Les nouveaux composants qui viennent d'être installés auront dans sa boîte de réception des messages provenant des archives de diffusion. À l'instar de la boîte de réception des messages, l'archive de diffusion peut également avoir une taille statique.

De plus, pour faire face à une situation où le routeur de messages lui-même est hors ligne, vous avez également besoin de boîtes d'envoi de messages. La boîte d'envoi de message fait partie du composant qui contient le message sortant temporairement.

Lie Ryan
la source