Em uma arquitetura orientada a eventos, cada componente atua apenas quando um evento é enviado pelo sistema.
Imagine um carro hipotético com um pedal de freio e uma luz de freio.
- As voltas da luz de freio sobre quando ele recebe um brake_on evento, e fora quando ele recebe um brake_off evento.
- O pedal do freio envia um evento brake_on quando é pressionado e um evento brake_off quando é liberado.
Tudo está bem, até que você tenha a situação em que o carro está ligado com o pedal do freio já pressionado . Como a luz do freio nunca recebeu um evento brake_on , ela permanecerá apagada - claramente uma situação indesejável. Ligar a luz do freio por padrão apenas reverte a situação.
O que poderia ser feito para resolver esse 'problema inicial de estado'?
EDIT: Obrigado por todas as respostas. Minha pergunta não era sobre um carro de verdade. Nos carros, eles resolviam esse problema enviando continuamente o estado - portanto, não há problema de inicialização nesse domínio. No meu domínio de software, essa solução usaria muitos ciclos desnecessários de CPU.
EDIT 2: Além da resposta de @ gbjbaanb , vou para um sistema em que:
- o pedal de freio hipotético, após a inicialização, envia um evento com seu estado e
- a luz de freio hipotética, após a inicialização, envia um evento solicitando um evento de estado do pedal do freio.
Com esta solução, não há dependências entre componentes, condições de corrida, filas de mensagens para ficar obsoletas e componentes 'mestre'.
fonte
initialize
) que contém os dados necessários do sensor.Respostas:
Existem várias maneiras de fazer isso, mas eu prefiro manter um sistema baseado em mensagens o mais dissociado possível. Isso significa que o sistema geral não pode ler o estado de nenhum componente, nem nenhum componente lê o estado de qualquer outro (pois dessa forma estão os espaguetes das dependências).
Portanto, embora o sistema em execução se cuide, precisamos de uma maneira de dizer a cada componente para iniciar, e já temos isso no registro de componentes, ou seja, na inicialização, o sistema principal deve informar cada componente que é agora registrado (ou solicitará que cada componente retorne seus detalhes para que possa ser registrado). Esse é o estágio no qual o componente pode executar suas tarefas de inicialização e pode enviar mensagens como faria na operação normal.
Assim, o pedal do freio, quando a ignição é iniciada, receberia uma mensagem de registro / verificação da gerência do carro e retornaria não apenas a mensagem "Estou aqui e trabalhando", mas também verificaria seu próprio estado e enviaria a mensagem mensagens para esse estado (por exemplo, uma mensagem pressionada no pedal).
O problema passa a ser uma das dependências de inicialização, como se a luz do freio ainda não estivesse registrada, ela não receberá a mensagem, mas isso é facilmente resolvido enfileirando todas essas mensagens até o sistema principal concluir a rotina de inicialização, registro e verificação .
O maior benefício é que não há código especial necessário para lidar com a inicialização, exceto que você já precisa escrever (ok, se o envio de mensagens para eventos do pedal do freio estiver em um manipulador do pedal do freio, você também precisará chamar isso na inicialização. , mas isso geralmente não é um problema, a menos que você tenha escrito esse código fortemente vinculado à lógica do manipulador) e nenhuma interação entre os componentes, exceto aqueles que eles já enviam uns aos outros normalmente. As arquiteturas de passagem de mensagens são muito boas por causa disso!
fonte
Você pode ter um evento de inicialização que defina os estados adequadamente após o carregamento / inicialização. Isso pode ser desejável para sistemas ou programas simples, que não incluem várias peças de hardware; no entanto, para sistemas mais complicados, com vários componentes físicos, você corre o mesmo risco de não inicializar - se um evento de "frenagem" for perdido ou perdido durante a comunicação. sistema (por exemplo, um sistema baseado em CAN), você pode inadvertidamente ajustar o sistema para trás, como se o tivesse iniciado com o freio pressionado. Quanto mais controladores você tiver, como em um carro, maior a probabilidade de que algo se perca.
Para explicar isso, você pode fazer com que a lógica do "freio ligado" envie repetidamente eventos "freio no". Talvez a cada 1/100 de segundo ou algo assim. Seu código que contém o cérebro pode escutar esses eventos e acionar o "freio" enquanto os recebe. Após 1/10 s de não receber sinais de "freio ligado", ele dispara um evento interno "freio_off".
Eventos diferentes terão requisitos de tempo consideravelmente diferentes. Em um carro, sua luz de freio precisa ser muito mais rápida do que a luz de verificação de combustível (onde um atraso de vários segundos é provavelmente aceitável) ou outros sistemas menos importantes.
A complexidade do seu sistema físico determinará qual dessas abordagens é mais apropriada. Dado que o seu exemplo é um veículo, você provavelmente desejaria algo semelhante ao último.
De qualquer forma, com um sistema físico, você NÃO deseja confiar em um único evento sendo recebido / processado corretamente. Microcontroladores conectados em um sistema em rede geralmente têm um tempo limite "Estou vivo" por esse motivo.
fonte
Nesse caso, eu não modelaria o freio como um simples ligar / desligar. Em vez disso, eu enviaria eventos de "pressão de freio". Por exemplo, uma pressão de 0 indicaria desligada e uma pressão de 100 seria totalmente deprimida. O sistema (nó) envia constantemente eventos de pressão de interrupção (em um determinado intervalo) ao (s) controlador (es), conforme necessário.
Quando o sistema foi iniciado, ele começava a receber eventos de pressão até ser desligado.
fonte
Se o seu único meio de transmitir informações de estado é através de eventos, você está com problemas. Em vez disso, você precisa ser capaz de:
A luz do freio pode ser vista como um observador do pedal do freio. Em outras palavras, o pedal do freio não sabe nada sobre a luz do freio e pode operar sem ela. (Isso significa que qualquer noção do pedal do freio enviando proativamente um evento de "estado inicial" para a luz do freio é mal concebida.)
Após a instanciação do sistema, a luz do freio se registra no pedal do freio para receber notificações de frenagem, e também lê o estado atual do pedal do freio e liga ou desliga automaticamente.
Em seguida, as notificações de frenagem podem ser implementadas de uma das três maneiras:
Prefiro a primeira abordagem, o que significa que, ao receber a notificação, a luz do freio simplesmente fará o que já sabe: ler o estado atual do pedal do freio e ligar ou desligar.
fonte
Em um sistema orientado a eventos (que atualmente uso e amo), acho importante manter as coisas o mais dissociadas possível. Então, com essa ideia em mente, vamos nos aprofundar.
É importante ter algum estado padrão. Sua luz de freio levaria o estado padrão de 'desligado' e seu pedal de freio levaria o estado padrão de 'ativo'. Qualquer alteração depois disso seria um evento.
Agora, para responder à sua pergunta. Imagine o seu pedal de freio sendo inicializado e pressionado, o evento dispara, mas ainda não há luzes de freio para receber o evento. Achei mais fácil separar a criação dos objetos (onde os ouvintes do evento seriam inicializados) como uma etapa separada antes de inicializar qualquer lógica. Isso impedirá qualquer condição de corrida, como você descreveu.
Também acho estranho usar dois eventos diferentes para o que é efetivamente a mesma coisa .
brake_off
ebrake_on
poderia ser simplificado eme_brake
com um parâmetrobool on
. Você pode simplificar seus eventos dessa maneira adicionando dados de suporte.fonte
O que você precisa é de um evento de transmissão e caixas de entrada de mensagens. Uma transmissão é uma mensagem publicada para um número não especificado de ouvintes. Um componente pode se inscrever em eventos de transmissão para receber apenas eventos nos quais está interessado. Isso fornece dissociação, pois o remetente não precisa saber quem são os destinatários. A tabela de inscrição precisa ser configurada estaticamente durante a instalação do componente (em vez de quando a inicialização). A caixa de entrada faz parte do roteador de mensagens que atua como um buffer para reter mensagens quando o componente de destino está offline.
O uso de faturas traz um problema, que é o tamanho da caixa de entrada. Você não deseja que o sistema retenha um número crescente de mensagens para componentes que nunca estarão mais online. Isso é importante, especialmente no sistema incorporado com restrições estritas de memória. Para superar o limite de tamanho da caixa de entrada, todas as mensagens transmitidas precisam seguir algumas regras. As regras são:
O nome da transmissão precisa ser declarado durante o tempo de instalação do componente. Se um componente envia uma segunda transmissão com o mesmo nome antes que o receptor processe a anterior, a nova transmissão substitui a anterior. Agora você pode ter um limite de tamanho de caixa de entrada estático, que pode garantir nunca exceder um determinado tamanho e pode ser pré-calculado com base nas tabelas de inscrição.
Por fim, você também precisa de um arquivo de transmissão. O arquivo de transmissão é uma tabela que contém o último evento de cada nome de transmissão. Os novos componentes recém-instalados terão sua caixa de entrada preenchida previamente com mensagens do arquivo de transmissão. Como a caixa de entrada de mensagens, o arquivo de transmissão também pode ter tamanho estático.
Além disso, para lidar com situações em que o roteador de mensagens está offline, você também precisa de caixas de saída de mensagens. A caixa de saída da mensagem faz parte do componente que retém a mensagem de saída temporariamente.
fonte