¿Cómo manejar el estado inicial en una arquitectura controlada por eventos?


33

En una arquitectura controlada por eventos, cada componente solo actúa cuando se envía un evento a través del sistema.

Imagine un automóvil hipotético con un pedal de freno y una luz de freno.

  • Las vueltas de luz de freno en cuando recibe una brake_on evento y apagado cuando recibe una brake_off evento.
  • El pedal de freno envía un evento brake_on cuando se presiona hacia abajo y un evento brake_off cuando se suelta.

Todo esto está muy bien, hasta que tenga la situación en la que el automóvil se enciende con el pedal del freno presionado . Como la luz de freno nunca recibió un evento brake_on , permanecerá apagada, claramente una situación indeseable. Encender la luz de freno por defecto solo revierte la situación.

¿Qué podría hacerse para resolver este 'problema de estado inicial'?

EDITAR: Gracias por todas las respuestas. Mi pregunta no era sobre un auto real. En los automóviles resolvieron este problema enviando continuamente el estado, por lo tanto, no hay ningún problema de inicio en ese dominio. En mi dominio de software, esa solución usaría muchos ciclos de CPU innecesarios.

EDIT 2: además de la respuesta de @ gbjbaanb , voy por un sistema en el que:

  • el hipotético pedal de freno, después de la inicialización, envía un evento con su estado, y
  • la hipotética luz de freno, después de la inicialización, envía un evento solicitando un evento de estado desde el pedal del freno.

Con esta solución, no hay dependencias entre componentes, no hay condiciones de carrera, no hay colas de mensajes que se vuelvan obsoletas y no hay componentes 'maestros'.


2
Lo primero que viene a la mente es generar un evento "sintético" (llamado initialize) que contiene los datos necesarios del sensor.
msw

¿No debería el pedal enviar un evento brake_pedal_on, y el freno real enviar el evento brake_on? No quisiera que se encienda la luz de freno si el freno no funciona.
bdsl

3
¿He mencionado que fue un ejemplo hipotético? :-) Se simplifica enormemente para mantener la pregunta breve y al grano.
Frank Kusters

Respuestas:


32

Hay muchas formas de hacer esto, pero prefiero mantener un sistema basado en mensajes lo más desacoplado posible. Esto significa que el sistema en general no puede leer el estado de ningún componente, ni ningún componente lee el estado de ningún otro (ya que de esa manera se encuentran los espaguetis de las dependencias).

Entonces, si bien el sistema en ejecución se cuidará solo, necesitamos una forma de decirle a cada componente que se inicie, y ya tenemos tal cosa en el registro de componentes, es decir, al inicio, el sistema central debe informar a cada componente que es ahora registrado (o pedirá a cada componente que devuelva sus detalles para que pueda registrarse). Esta es la etapa en la que el componente puede realizar sus tareas de inicio y puede enviar mensajes como lo haría en una operación normal.

Entonces, el pedal del freno, cuando se inicia el encendido, recibiría un mensaje de registro / verificación de la administración del automóvil y devolvería no solo un mensaje de "Estoy aquí y trabajando", sino que luego verificaría su propio estado y enviará el mensajes para ese estado (por ejemplo, un mensaje de pedal pisado).

El problema se convierte en una dependencia de inicio, ya que si la luz de freno aún no está registrada, entonces no recibirá el mensaje, pero esto se resuelve fácilmente poniendo en cola todos estos mensajes hasta que el sistema central haya completado su rutina de inicio, registro y verificación .

El mayor beneficio es que no se requiere un código especial para manejar la inicialización, excepto que ya tiene que escribir (bueno, si su envío de mensajes para eventos de pedal de freno está en un controlador de pedal de freno, también deberá llamarlo en su inicialización , pero eso generalmente no es un problema a menos que haya escrito ese código fuertemente ligado a la lógica del controlador) y no haya interacción entre los componentes, excepto aquellos que ya se envían entre sí de manera normal. ¡Las arquitecturas que pasan mensajes son muy buenas por esto!


1
Me gusta su respuesta, ya que mantiene todos los componentes desacoplados: fue la razón más importante para elegir esta arquitectura. Sin embargo, actualmente no existe un componente 'maestro' real que decida que el sistema está en un estado 'inicializado': todo comienza a ejecutarse. Con el problema en mi pregunta como resultado. Una vez que el maestro decide que el sistema se está ejecutando, puede enviar un evento 'sistema inicializado' a todos los componentes, después de lo cual cada componente comienza a transmitir su estado. Problema resuelto. ¡Gracias! (Ahora solo me queda el problema de cómo decidir si el sistema se inicializa ...)
Frank Kusters

¿Qué tal si el despachador de actualizaciones de estado realiza un seguimiento de la actualización más reciente recibida de cada objeto, y cada vez que se recibe una nueva solicitud de suscripción, debe enviar al nuevo suscriptor las actualizaciones más recientes que ha recibido de las fuentes de eventos registradas?
supercat

En ese caso, también debe realizar un seguimiento de cuándo caducan los eventos. No todos los eventos son susceptibles de mantenerse para siempre para cualquier componente nuevo que pueda registrarse.
Frank Kusters

@spaceknarf bueno, en el caso de que "todo comienza a funcionar", no se puede construir dependencia en los componentes, por lo que el pedal se inicia después de la luz, solo tendrá que iniciarlos en ese orden, aunque imagino que algo los comienza a ejecutar, así que ejecute ellos en el orden 'correcto' (por ejemplo, scripts de inicio de inicio de Linux antes de systemd donde el servicio que se inicia primero se llama 1.xxx y el segundo se llama 2.xxx, etc.).
gbjbaanb

Los guiones con un orden como ese son frágiles. Contiene muchas dependencias implícitas. En cambio, estaba pensando que si tiene un componente 'maestro', que tiene una lista de componentes configurados estáticamente que deberían ejecutarse (como lo menciona @Lie Ryan), entonces puede transmitir un evento 'listo' una vez que se cargan todos esos componentes. En respuesta a eso, todos los componentes transmiten su estado inicial.
Frank Kusters

4

Puede tener un evento de inicialización que establezca los estados adecuadamente al cargar / iniciar. Esto puede ser deseable para sistemas o programas simples que no incluyen múltiples piezas de hardware, sin embargo, para sistemas más complicados con múltiples componentes físicos, ya que corre el mismo riesgo de no inicializar en absoluto, si se pierde o se pierde un evento de "freno" a lo largo de su comunicación sistema (por ejemplo, un sistema basado en CAN) puede inadvertidamente configurar su sistema hacia atrás como si lo iniciara con el freno presionado. Cuantos más controladores tenga, como un automóvil, mayor será la probabilidad de que se pierda algo.

Para tener esto en cuenta, puede hacer que la lógica "freno activado" envíe repetidamente eventos "freno activado". Quizás cada 1/100 de segundo o algo así. Su código que contiene el cerebro puede escuchar estos eventos y activar el "freno" mientras los recibe. Después de 1 / 10seg de no recibir señales de "freno activado", se activa un evento interno "freno_desconectado".

Diferentes eventos tendrán requisitos de tiempo considerablemente diferentes. En un automóvil, la luz de freno debe ser mucho más rápida que la luz de control de combustible (donde probablemente sea aceptable un retraso de varios segundos) u otros sistemas menos importantes.

La complejidad de su sistema físico determinará cuál de estos enfoques es más apropiado. Dado que su ejemplo es un vehículo, es probable que desee algo similar a este último.

De cualquier manera, con un sistema físico, NO desea confiar en que un solo evento se reciba / procese correctamente. Los microcontroladores conectados en un sistema en red a menudo tienen un tiempo de espera "Estoy vivo" por este motivo.


en un sistema físico, tendrías un cable y utilizarías una lógica binaria: ALTO está apretado el freno y BAJO el freno no está presionado
loco de trinquete

@ratchetfreak hay muchas posibilidades para este tipo de cosas. Quizás un interruptor pueda manejar eso. Hay muchos otros eventos del sistema que no se manejan así de simple.
enderland

1

En este caso, no modelaría el freno como un simple encendido / apagado. Más bien, enviaría eventos de "presión de frenado". Por ejemplo, una presión de 0 indicaría apagado y una presión de 100 estaría completamente deprimida. El sistema (nodo) enviaría constantemente eventos de presión de rotura (a cierto intervalo) a los controladores según sea necesario.

Cuando se inicia el sistema, comenzará a recibir eventos de presión hasta que se apague.


1

Si su único medio de pasar información de estado es a través de eventos, entonces está en problemas. En cambio, debe ser capaz de ambos:

  1. consultar el estado actual del pedal de freno, y
  2. regístrese para eventos de "estado cambiado" desde el pedal del freno.

La luz de freno puede verse como un observador del pedal de freno. En otras palabras, el pedal del freno no sabe nada sobre la luz de freno y puede funcionar sin ella. (Esto significa que cualquier idea de que el pedal del freno envía de manera proactiva un evento de "estado inicial" a la luz de freno está mal concebida).

Al crear una instancia del sistema, la luz de freno se registra con el pedal del freno para recibir notificaciones de frenado, y también lee el estado actual del pedal del freno y se enciende o apaga.

Luego, las notificaciones de frenado se pueden implementar de tres maneras:

  1. como eventos sin parámetros del "estado del pedal de frenado cambiado"
  2. como un par de eventos de "pedal de freno ahora presionado" y eventos de "pedal de freno ahora liberado"
  3. como un evento de "nuevo estado de pedal de ruptura" con un parámetro "deprimido" o "liberado".

Prefiero el primer enfoque, lo que significa que al recibir la notificación, la luz de freno simplemente hará lo que ya sabe cómo hacer: leer el estado actual del pedal del freno y encenderse o apagarse.


0

En un sistema basado en eventos (que actualmente uso y amo), encuentro importante mantener las cosas lo más desacopladas posible. Entonces, con esa idea en mente, profundicemos.

Es importante tener un estado predeterminado. Su luz de freno tomaría el estado predeterminado de 'apagado' y su pedal de freno tomaría el estado predeterminado de 'arriba'. Cualquier cambio después de eso sería un evento.

Ahora para abordar su pregunta. Imagine que su pedal de freno se inicializa y presiona, el evento se dispara, pero todavía no hay luces de freno para recibir el evento. He encontrado que es más fácil separar la creación de los objetos (donde los oyentes de eventos se inicializarían) como un paso separado antes de inicializar cualquier lógica. Eso evitará cualquier condición de carrera como la has descrito.

También me resulta incómodo usar dos eventos diferentes para lo que efectivamente es lo mismo . brake_offy brake_onpodría simplificarse e_brakecon un parámetro bool on. Puede simplificar sus eventos de esta manera agregando datos de respaldo.


0

Lo que necesita es un evento de difusión y bandejas de entrada de mensajes. Una transmisión es un mensaje que se publica a un número no especificado de oyentes. Un componente puede suscribirse a eventos de difusión, por lo que solo recibe eventos en los que está interesado. Esto proporciona desacoplamiento, ya que el remitente no necesita saber quiénes son los receptores. La tabla de suscripción debe configurarse estáticamente durante la instalación del componente (en lugar de cuando se inicializa). La bandeja de entrada es una parte del enrutador de mensajes que actúa como un búfer para contener mensajes cuando el componente de destino está fuera de línea.

El uso de facturas trae un problema, que es el tamaño de la bandeja de entrada. No desea que el sistema tenga que retener un número creciente de mensajes para componentes que nunca más estarán en línea. Esto es importante especialmente con sistemas embebidos con restricciones estrictas de memoria. Para superar el límite de tamaño de la bandeja de entrada, todos los mensajes emitidos deben seguir algunas reglas. Las reglas son:

  1. cada evento de transmisión requiere un nombre
  2. en cualquier momento, el remitente de un evento de transmisión solo puede tener una transmisión activa con un nombre específico
  3. el efecto causado por el evento debe ser idempotente

El nombre de difusión debe declararse durante el tiempo de instalación del componente. Si un componente envía una segunda transmisión con el mismo nombre antes de que el receptor procese la anterior, la nueva transmisión sobrescribe la anterior. Ahora puede tener un límite de tamaño de bandeja de entrada estático, que puede garantizarse que nunca superará un determinado tamaño y puede calcularse previamente en función de las tablas de suscripción.

Finalmente, también necesita un archivo de difusión. El archivo de difusión es una tabla que contiene el último evento de cada nombre de difusión. Los nuevos componentes que se acaban de instalar tendrán su bandeja de entrada prepoblada con mensajes del archivo de difusión. Al igual que la bandeja de entrada de mensajes, el archivo de difusión también puede tener un tamaño estático.

Además, para lidiar con situaciones en las que el enrutador de mensajes está fuera de línea, también necesita bandejas de salida de mensajes. La bandeja de salida de mensajes es parte del componente que contiene el mensaje saliente temporalmente.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.