Come gestire lo stato iniziale in un'architettura guidata dagli eventi?


33

In un'architettura guidata dagli eventi, ciascun componente agisce solo quando un evento viene inviato attraverso il sistema.

Immagina un'auto ipotetica con un pedale del freno e una luce dei freni.

  • La luce del freno si accende quando riceve un evento Brake_on e si spegne quando riceve un evento Brake_off .
  • Il pedale del freno invia un evento Brake_on quando viene premuto e un evento Brake_off quando viene rilasciato.

Va tutto bene, fino a quando non si verifica la situazione in cui l'auto viene accesa con il pedale del freno già premuto . Poiché la luce del freno non ha mai ricevuto un evento Brake_on , rimarrà spenta, una situazione chiaramente indesiderabile. L'accensione della luce di stop di default inverte solo la situazione.

Cosa si potrebbe fare per risolvere questo "problema di stato iniziale"?

EDIT: grazie per tutte le risposte. La mia domanda non riguardava un'auto vera. Nelle auto hanno risolto questo problema inviando continuamente lo stato - quindi non c'è nessun problema di avvio in quel dominio. Nel mio dominio del software, quella soluzione userebbe molti cicli CPU non necessari.

EDIT 2: Oltre alla risposta di @ gbjbaanb , vado per un sistema in cui:

  • l'ipotetico pedale del freno, dopo l'inizializzazione, invia un evento con il suo stato e
  • l'ipotetica luce di stop, dopo l'inizializzazione, invia un evento che richiede un evento di stato dal pedale del freno.

Con questa soluzione, non ci sono dipendenze tra i componenti, nessuna condizione di competizione, nessuna coda di messaggi per diventare obsoleta e nessun componente "master".


2
La prima cosa che mi viene in mente è generare un evento "sintetico" (chiamalo initialize) che contenga i dati del sensore necessari.
msw,

Il pedale non dovrebbe inviare un evento Brake_pedal_on e il freno effettivo deve inviare l'evento Brake_on? Non vorrei che la mia luce del freno si accendesse se il freno non funzionava.
bdsl,

3
Ho già detto che era un esempio ipotetico? :-) È fortemente semplificato per mantenere la domanda breve e puntuale.
Frank Kusters,

Risposte:


32

Esistono molti modi per farlo, ma preferisco mantenere un sistema basato sui messaggi il più possibile disaccoppiato. Ciò significa che il sistema complessivo non può leggere lo stato di nessun componente, né nessun componente legge lo stato di nessun altro (poiché in questo modo risiedono i legami degli spaghetti delle dipendenze).

Quindi, mentre il sistema in esecuzione si prenderà cura di se stesso, abbiamo bisogno di un modo per dire a ciascun componente di avviarsi da solo, e abbiamo già una cosa del genere nella registrazione dei componenti, cioè all'avvio il sistema principale deve informare ogni componente che è ora registrato (o chiederà a ciascun componente di restituire i dettagli in modo che possa essere registrato). Questa è la fase in cui il componente può eseguire le sue attività di avvio e può inviare messaggi come farebbe durante il normale funzionamento.

Quindi il pedale del freno, quando viene avviata l'accensione, riceveva un messaggio di registrazione / controllo dalla direzione dell'auto e restituiva non solo un messaggio "Sono qui e lavoro", ma controllava quindi il suo stato e inviava il messaggi per quello stato (ad esempio un messaggio premuto il pedale).

Il problema diventa quindi una delle dipendenze all'avvio, come se la luce del freno non fosse ancora registrata, quindi non riceverà il messaggio, ma questo può essere facilmente risolto accodando tutti questi messaggi fino a quando il sistema principale non ha completato la sua procedura di avvio, registrazione e controllo .

Il più grande vantaggio è che non è necessario un codice speciale per gestire l'inizializzazione, tranne per il fatto che devi già scrivere (ok, se l'invio di messaggi per gli eventi del pedale del freno è in un gestore del pedale del freno, dovrai chiamarlo anche nella tua inizializzazione , ma di solito non è un problema a meno che tu non abbia scritto quel codice fortemente legato alla logica del gestore) e nessuna interazione tra i componenti tranne quelli che si sono già scambiati tra loro normalmente. Le architetture di passaggio dei messaggi sono molto buone per questo!


1
Mi piace la tua risposta, poiché mantiene tutti i componenti disaccoppiati - è stato il motivo più importante per scegliere questa architettura. Tuttavia, attualmente non esiste un vero componente "master" che decide che il sistema è in uno stato "inizializzato": tutto inizia a funzionare. Di conseguenza il problema nella mia domanda. Una volta che il master decide che il sistema è in esecuzione, può inviare un evento "inizializzato dal sistema" a tutti i componenti, dopodiché ogni componente inizia a trasmettere il proprio stato. Problema risolto. Grazie! (Ora mi resta solo il problema su come decidere se il sistema è inizializzato ...)
Frank Kusters,

Che ne dici di fare in modo che il dispatcher di aggiornamento dello stato tenga traccia dell'aggiornamento più recente ricevuto da ciascun oggetto e ogni volta che viene ricevuta una nuova richiesta di abbonamento, ha inviato al nuovo abbonato gli aggiornamenti più recenti che ha ricevuto dalle fonti degli eventi registrati?
supercat,

In tal caso, devi anche tenere traccia di quando scadono gli eventi. Non tutti gli eventi sono suscettibili di rimanere in giro per sempre per eventuali nuovi componenti che potrebbero essere registrati.
Frank Kusters,

@spaceknarf bene, nel caso in cui "tutto inizia a funzionare" non puoi creare dipendenza nei componenti, quindi il pedale inizia dopo la luce, dovrai solo avviarli in questo ordine, anche se immagino che qualcosa li avvii, quindi corri nell'ordine "giusto" (ad es. script di avvio di avvio di Linux prima di systemd dove il servizio da avviare per primo si chiama 1.xxx e il secondo si chiama 2.xxx ecc.).
gbjbaanb,

Gli script con un ordine del genere sono fragili. Contiene molte dipendenze implicite. Invece, stavo pensando se hai un componente 'master', che ha un elenco staticamente configurato di componenti che dovrebbero essere eseguiti (come menzionato da @Lie Ryan), quindi può trasmettere un evento 'pronto' una volta caricati tutti quei componenti. In risposta a ciò, tutti i componenti trasmettono il loro stato iniziale.
Frank Kusters,

4

È possibile avere un evento di inizializzazione che imposta gli stati in modo appropriato al caricamento / avvio. Ciò può essere desiderabile per sistemi o programmi semplici che non includano più parti hardware, tuttavia per sistemi più complicati con più componenti fisici poiché si corre lo stesso rischio di non inizializzarsi affatto - se un evento di "frenata" viene perso o perso durante la comunicazione sistema (ad esempio un sistema basato su CAN) è possibile inavvertitamente impostare il sistema all'indietro come se fosse stato avviato con il freno premuto. Più controller potresti avere, come con un'auto, maggiore è la probabilità che manchi qualcosa.

Per tenere conto di ciò, è possibile avere la logica "freno attivo" che invia ripetutamente eventi "freno attivo". Forse ogni 1/100 di secondo o qualcosa del genere. Il tuo codice contenente il cervello può ascoltare questi eventi e innescare "frenare" mentre li sta ricevendo. Dopo 1 / 10sec di mancata ricezione dei segnali "Brake on", si attiva un evento interno "Brake_off".

Eventi diversi avranno requisiti di tempistica considerevolmente diversi. In un'auto, la luce del freno deve essere molto più veloce di quanto si pensi alla luce del carburante di controllo (dove è probabilmente accettabile un ritardo di più secondi) o altri sistemi meno importanti.

La complessità del tuo sistema fisico determinerà quale di questi approcci è più appropriato. Dato che il tuo esempio è un veicolo, probabilmente vorresti qualcosa di simile a quest'ultimo.

In entrambi i casi, con un sistema fisico, NON si desidera fare affidamento su un singolo evento ricevuto / elaborato correttamente. Per questo motivo, i microcontrollori collegati su un sistema in rete hanno spesso un timeout "I'm alive".


in un sistema fisico dovresti far passare un filo e usare la logica binaria: ALTO è premuto il freno e BASSO non è premuto il freno
maniaco del cricchetto

@ratchetfreak ci sono molte possibilità per questo genere di cose. Forse un interruttore può gestirlo. Ci sono molti altri eventi di sistema che non sono gestiti così semplicemente.
Enderland,

1

In questo caso, non modellerei il freno come un semplice on / off. Al contrario, invierei eventi di "pressione del freno". Ad esempio, una pressione di 0 indicherebbe off e una pressione di 100 sarebbe completamente depressa. Il sistema (nodo) invierebbe costantemente eventi di interruzione della pressione (a un certo intervallo) ai controllori, se necessario.

All'avvio del sistema, iniziava a ricevere gli eventi di pressione fino allo spegnimento.


1

Se il tuo unico mezzo per trasmettere informazioni sullo stato è attraverso gli eventi, allora sei nei guai. Invece, devi essere in grado di entrambi:

  1. interrogare lo stato corrente del pedale del freno e
  2. registrarsi per eventi "stato modificato" dal pedale del freno.

La luce del freno può essere vista come un osservatore del pedale del freno. In altre parole, il pedale del freno non sa nulla della luce del freno e può funzionare senza di essa. (Ciò significa che qualsiasi nozione del pedale del freno che invia in modo proattivo un evento di "stato iniziale" alla luce del freno è mal concepita.)

All'istanza del sistema, la luce del freno si registra con il pedale del freno per ricevere le notifiche di frenata, inoltre legge lo stato corrente del pedale del freno e si accende o si spegne.

Quindi, le notifiche di frenatura possono essere implementate in tre modi:

  1. come eventi senza parametri "stato del pedale di frenatura modificato"
  2. poiché una coppia di eventi "il pedale del freno è ora premuto" e "il pedale del freno è ora rilasciato"
  3. come evento "nuovo stato del pedale di rottura" con un parametro "depresso" o "rilasciato".

Preferisco il primo approccio, il che significa che alla ricezione della notifica, la luce del freno farà semplicemente ciò che già sa fare: leggere lo stato corrente del pedale del freno e accendere o spegnere se stesso.


0

In un sistema guidato da eventi (che attualmente utilizzo e adoro), trovo importante mantenere le cose il più possibile disaccoppiate. Quindi, con questa idea in mente, approfondiamo.

È importante avere uno stato predefinito. La tua luce del freno avrebbe lo stato predefinito di "off" e il pedale del freno avrebbe lo stato predefinito di "su". Eventuali modifiche successive sarebbero un evento.

Ora per rispondere alla tua domanda. Immagina che il tuo pedale del freno sia inizializzato e premuto, l'evento si accende, ma non ci sono ancora luci di arresto per ricevere l'evento. Ho trovato più semplice separare la creazione degli oggetti (in cui i listener di eventi sarebbero stati inizializzati) come un passaggio separato prima di inizializzare qualsiasi logica. Ciò impedirà qualsiasi condizione di gara come da te descritto.

Trovo anche imbarazzante usare due eventi diversi per quello che è effettivamente la stessa cosa . brake_offe brake_onpotrebbe essere semplificato e_brakecon un parametro bool on. Puoi semplificare i tuoi eventi in questo modo aggiungendo dati di supporto.


0

Ciò di cui hai bisogno è un evento di trasmissione e messaggi in arrivo. Una trasmissione è un messaggio che viene pubblicato per un numero non specificato di ascoltatori. Un componente può iscriversi per eventi di trasmissione in modo da ricevere solo eventi a cui è interessato. Ciò consente il disaccoppiamento, poiché il mittente non deve sapere chi sono i destinatari. La tabella delle sottoscrizioni deve essere configurata staticamente durante l'installazione del componente (anziché quando viene inizializzato). La posta in arrivo fa parte del router dei messaggi che funge da buffer per contenere i messaggi quando il componente di destinazione è offline.

L'uso delle fatture comporta un problema, ovvero la dimensione della posta in arrivo. Non si desidera che il sistema debba conservare un numero crescente di messaggi per componenti che non saranno più online. Questo è importante soprattutto con i sistemi embedded con rigidi limiti di memoria. Per superare il limite di dimensioni della posta in arrivo, tutti i messaggi trasmessi devono seguire alcune regole. Le regole sono:

  1. ogni evento di trasmissione richiede un nome
  2. in qualsiasi momento, il mittente di un evento di trasmissione può avere solo una trasmissione attiva con un nome specificato
  3. l'effetto causato dall'evento deve essere idempotente

Il nome della trasmissione deve essere dichiarato durante il tempo di installazione del componente. Se un componente invia una seconda trasmissione con lo stesso nome prima che il ricevitore elabori la precedente, la nuova trasmissione sovrascrive la precedente. Ora puoi avere un limite di dimensioni della posta in arrivo statica, che può essere garantito per non superare mai una determinata dimensione e può essere precalcolato in base alle tabelle di abbonamento.

Infine, è necessario anche un archivio di trasmissione. L'archivio broadcast è una tabella che contiene l'ultimo evento di ciascun nome broadcast. I nuovi componenti appena installati avranno la posta in arrivo prepopolata con i messaggi dall'archivio broadcast. Come la posta in arrivo dei messaggi, anche l'archivio broadcast può avere dimensioni statiche.

Inoltre, per gestire la situazione in cui il router di messaggi stesso non è in linea, sono necessarie anche le caselle di posta in uscita. La posta in uscita del messaggio fa parte del componente che contiene temporaneamente il messaggio in uscita.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.