Exemplo simples de máquina de estado em C #?


258

Atualizar:

Mais uma vez obrigado pelos exemplos, eles foram muito úteis e, com o seguinte, não pretendo tirar nada deles.

Não são os exemplos atualmente dados, até onde eu os entendo e máquinas de estado, apenas metade do que geralmente entendemos por uma máquina de estado?
No sentido de que os exemplos mudam de estado, mas isso é representado apenas pela alteração do valor de uma variável (e permitindo diferentes alterações de valor em diferentes estados), enquanto geralmente uma máquina de estado também deve mudar seu comportamento, e comportamento não (somente) em o senso de permitir que valores diferentes mudem para uma variável dependendo do estado, mas no sentido de permitir que diferentes métodos sejam executados para diferentes estados.

Ou tenho um conceito errado de máquinas de estado e seu uso comum?

Cumprimentos


Pergunta original:

Eu encontrei essa discussão sobre máquinas de estado e blocos de iteradores em c # e ferramentas para criar máquinas de estado e o que não era para C #, então achei muitas coisas abstratas, mas como noob tudo isso é um pouco confuso.

Portanto, seria ótimo se alguém pudesse fornecer um exemplo de código-fonte em C # que realize uma máquina de estado simples com talvez 3,4 estados, apenas para entender o essencial.



Você está se perguntando sobre as máquinas de estado em geral ou apenas as baseadas em iteradores?
Skurmedel

2
Existe .net Core Stateless lib com exemplos, DAGs daigram etc. - vale a pena revisar: hanselman.com/blog/…
zmische

Respostas:


416

Vamos começar com este diagrama de estado simples:

diagrama de máquina de estado simples

Nós temos:

  • 4 estados (inativo, ativo, pausado e encerrado)
  • 5 tipos de transições de estado (comando de início, comando de finalização, comando de pausa, comando de retomada e comando de saída).

Você pode convertê-lo em C # de várias maneiras, como executar uma instrução switch no estado e comando atuais ou procurar transições em uma tabela de transição. Para esta máquina de estado simples, prefiro uma tabela de transição, que é muito fácil de representar usando Dictionary:

using System;
using System.Collections.Generic;

namespace Juliet
{
    public enum ProcessState
    {
        Inactive,
        Active,
        Paused,
        Terminated
    }

    public enum Command
    {
        Begin,
        End,
        Pause,
        Resume,
        Exit
    }

    public class Process
    {
        class StateTransition
        {
            readonly ProcessState CurrentState;
            readonly Command Command;

            public StateTransition(ProcessState currentState, Command command)
            {
                CurrentState = currentState;
                Command = command;
            }

            public override int GetHashCode()
            {
                return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                StateTransition other = obj as StateTransition;
                return other != null && this.CurrentState == other.CurrentState && this.Command == other.Command;
            }
        }

        Dictionary<StateTransition, ProcessState> transitions;
        public ProcessState CurrentState { get; private set; }

        public Process()
        {
            CurrentState = ProcessState.Inactive;
            transitions = new Dictionary<StateTransition, ProcessState>
            {
                { new StateTransition(ProcessState.Inactive, Command.Exit), ProcessState.Terminated },
                { new StateTransition(ProcessState.Inactive, Command.Begin), ProcessState.Active },
                { new StateTransition(ProcessState.Active, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Active, Command.Pause), ProcessState.Paused },
                { new StateTransition(ProcessState.Paused, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Paused, Command.Resume), ProcessState.Active }
            };
        }

        public ProcessState GetNext(Command command)
        {
            StateTransition transition = new StateTransition(CurrentState, command);
            ProcessState nextState;
            if (!transitions.TryGetValue(transition, out nextState))
                throw new Exception("Invalid transition: " + CurrentState + " -> " + command);
            return nextState;
        }

        public ProcessState MoveNext(Command command)
        {
            CurrentState = GetNext(command);
            return CurrentState;
        }
    }


    public class Program
    {
        static void Main(string[] args)
        {
            Process p = new Process();
            Console.WriteLine("Current State = " + p.CurrentState);
            Console.WriteLine("Command.Begin: Current State = " + p.MoveNext(Command.Begin));
            Console.WriteLine("Command.Pause: Current State = " + p.MoveNext(Command.Pause));
            Console.WriteLine("Command.End: Current State = " + p.MoveNext(Command.End));
            Console.WriteLine("Command.Exit: Current State = " + p.MoveNext(Command.Exit));
            Console.ReadLine();
        }
    }
}

Por uma questão de preferência pessoal, eu gosto de projetar minhas máquinas de estado com uma GetNextfunção para retornar o próximo estado deterministicamente e uma MoveNextfunção para alterar a máquina de estado.


66
+1 para a implementação correta do GetHashCode()uso de números primos.
ja72

13
Você poderia me explicar o objetivo de GetHashCode ()?
Siddharth

14
@ Sidharth: A StateTransitionclasse é usada como chave no dicionário e a igualdade de chaves é importante. Duas instâncias distintas de StateTransitiondevem ser consideradas iguais desde que representem a mesma transição (por exemplo, CurrentStatee Commandsejam iguais). Para implementar a igualdade, você deve substituir Equalstambém GetHashCode. Em particular, o dicionário usará o código hash e dois objetos iguais devem retornar o mesmo código hash. Você também obtém um bom desempenho se não houver muitos objetos diferentes iguais que compartilhem o mesmo código de hash, e é por isso que GetHashCodeé implementado como mostrado.
Martin Liversage

14
Embora isso certamente proporcione uma máquina de estado (e uma implementação adequada de C # '), sinto que ainda falta a resposta à pergunta do OP sobre a mudança de comportamento? Afinal, ele apenas calcula os estados, mas o comportamento relacionado às mudanças de estado, a carne real do programa e geralmente chamada de eventos de Entrada / Saída, ainda está ausente.
stijn

2
Se alguém vai precisar: ajustei esta máquina de tate e usei-a no meu jogo de união. Está disponível no git hub: github.com/MarcoMig/Finite-State-Machine-FSM
Max_Power89

73

Você pode querer usar uma das máquinas de estado finito de código aberto existentes. Por exemplo, bbv.Common.StateMachine encontrado em http://code.google.com/p/bbvcommon/wiki/StateMachine . Possui uma sintaxe fluente muito intuitiva e muitos recursos, como ações de entrada / saída, ações de transição, guardas, implementação hierárquica, passiva (executada no encadeamento do chamador) e implementação ativa (encadeamento próprio no qual o fsm é executado, eventos são adicionados a uma fila).

Tomando o exemplo de Juliets, a definição para a máquina de estados fica muito fácil:

var fsm = new PassiveStateMachine<ProcessState, Command>();
fsm.In(ProcessState.Inactive)
   .On(Command.Exit).Goto(ProcessState.Terminated).Execute(SomeTransitionAction)
   .On(Command.Begin).Goto(ProcessState.Active);
fsm.In(ProcessState.Active)
   .ExecuteOnEntry(SomeEntryAction)
   .ExecuteOnExit(SomeExitAction)
   .On(Command.End).Goto(ProcessState.Inactive)
   .On(Command.Pause).Goto(ProcessState.Paused);
fsm.In(ProcessState.Paused)
   .On(Command.End).Goto(ProcessState.Inactive).OnlyIf(SomeGuard)
   .On(Command.Resume).Goto(ProcessState.Active);
fsm.Initialize(ProcessState.Inactive);
fsm.Start();

fsm.Fire(Command.Begin);

Atualização : o local do projeto foi movido para: https://github.com/appccelerate/statemachine


4
Obrigado por referenciar esta excelente máquina de estado de código aberto. Posso perguntar como posso obter o estado atual?
Ramazan Polat

3
Você não pode e não deveria. Estado é algo instável. Quando você solicita o estado, é possível que você esteja no meio de uma transição. Todas as ações devem ser realizadas dentro de transições, entrada e saída de estado. Se você realmente deseja ter o estado, pode adicionar um campo local e atribuir o estado em uma ação de entrada.
Remo Gloor

4
A questão é: do que você "precisa" e se realmente precisa do estado SM ou de algum outro tipo de estado. Por exemplo, se você precisar de algum texto de exibição, vários declarados poderão ter o mesmo texto de exibição, por exemplo, se a preparação para o envio tiver vários sub-estados. Nesse caso, você deve fazer exatamente o que pretende fazer. Atualize algum texto de exibição nos lugares corretos. Por exemplo, dentro de ExecuteOnEntry. Se precisar de mais informações, faça uma nova pergunta e indique exatamente o seu problema, pois isso está saindo do tópico aqui.
Remo Gloor

Ok, estou fazendo uma nova pergunta e esperando que você responda. Porque acho que ninguém mais resolve esse problema, pois você tem a melhor resposta, mas ainda assim o questionador não aceitou. Vou postar o URL da pergunta aqui. Obrigado.
Ramazan Polat

4
+1 para a API fluente e declarativa. É incrivel. BTW, o código do google parece estar desatualizado. O mais novo site de projeto está no GitHub aqui
KFL

52

Aqui está um exemplo de uma máquina de estado finito muito clássica, modelando um dispositivo eletrônico muito simplificado (como uma TV)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace fsm
{
class Program
{
    static void Main(string[] args)
    {
        var fsm = new FiniteStateMachine();
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.PlugIn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOff);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.RemovePower);
        Console.WriteLine(fsm.State);
        Console.ReadKey();
    }

    class FiniteStateMachine
    {
        public enum States { Start, Standby, On };
        public States State { get; set; }

        public enum Events { PlugIn, TurnOn, TurnOff, RemovePower };

        private Action[,] fsm;

        public FiniteStateMachine()
        {
            this.fsm = new Action[3, 4] { 
                //PlugIn,       TurnOn,                 TurnOff,            RemovePower
                {this.PowerOn,  null,                   null,               null},              //start
                {null,          this.StandbyWhenOff,    null,               this.PowerOff},     //standby
                {null,          null,                   this.StandbyWhenOn, this.PowerOff} };   //on
        }
        public void ProcessEvent(Events theEvent)
        {
            this.fsm[(int)this.State, (int)theEvent].Invoke();
        }

        private void PowerOn() { this.State = States.Standby; }
        private void PowerOff() { this.State = States.Start; }
        private void StandbyWhenOn() { this.State = States.Standby; }
        private void StandbyWhenOff() { this.State = States.On; }
    }
}
}

6
para quem é iniciante em máquinas de estado, este é um excelente primeiro exemplo para molhar os pés primeiro.
PositiveGuy

2
Eu sou novo em máquinas de estado e, sério, isso me trouxe a luz - obrigado!
MC5

1
Gostei desta implementação. Para quem pode tropeçar nisso, uma ligeira "melhoria". Na classe FSM, adicionei private void DoNothing() {return;}e substitui todas as instâncias de null por this.DoNothing. Tem o agradável efeito colateral de retornar o estado atual.
Sethmo011

1
Gostaria de saber se há um raciocínio por trás de alguns desses nomes. Quando olho para isso, minha primeira intuição é renomear os elementos do Statespara Unpowered, Standby, On. Meu raciocínio é que, se alguém me perguntasse em que estado minha televisão está, eu diria "Desligado" e não "Iniciar". Eu também mudei StandbyWhenOne StandbyWhenOffpara TurnOne TurnOff. Isso faz com que o código seja lido de maneira mais intuitiva, mas me pergunto se existem convenções ou outros fatores que tornam minha terminologia menos apropriada.
Jason Hamje

Parece razoável, eu realmente não estava seguindo nenhuma convenção de nomenclatura estadual; nome como faz sentido para qualquer modelo.
Pete Stensønes 6/11/19

20

Algumas promoções sem vergonha aqui, mas há um tempo atrás eu criei uma biblioteca chamada YieldMachine que permite que uma máquina de estado de complexidade limitada seja descrita de uma maneira muito limpa e simples. Por exemplo, considere uma lâmpada:

máquina de estado de uma lâmpada

Observe que esta máquina de estado possui 2 gatilhos e 3 estados. No código YieldMachine, escrevemos um método único para todo comportamento relacionado ao estado, no qual cometemos a atrocidade horrível de usar gotopara cada estado. Um gatilho se torna uma propriedade ou campo do tipo Action, decorado com um atributo chamado Trigger. Eu comentei o código do primeiro estado e suas transições abaixo; os próximos estados seguem o mesmo padrão.

public class Lamp : StateMachine
{
    // Triggers (or events, or actions, whatever) that our
    // state machine understands.
    [Trigger]
    public readonly Action PressSwitch;

    [Trigger]
    public readonly Action GotError;

    // Actual state machine logic
    protected override IEnumerable WalkStates()
    {
    off:                                       
        Console.WriteLine("off.");
        yield return null;

        if (Trigger == PressSwitch) goto on;
        InvalidTrigger();

    on:
        Console.WriteLine("*shiiine!*");
        yield return null;

        if (Trigger == GotError) goto error;
        if (Trigger == PressSwitch) goto off;
        InvalidTrigger();

    error:
        Console.WriteLine("-err-");
        yield return null;

        if (Trigger == PressSwitch) goto off;
        InvalidTrigger();
    }
}

Curto e legal, hein!

Essa máquina de estado é controlada simplesmente enviando gatilhos para ela:

var sm = new Lamp();
sm.PressSwitch(); //go on
sm.PressSwitch(); //go off

sm.PressSwitch(); //go on
sm.GotError();    //get error
sm.PressSwitch(); //go off

Apenas para esclarecer, adicionamos alguns comentários ao primeiro estado para ajudar você a entender como usar isso.

    protected override IEnumerable WalkStates()
    {
    off:                                       // Each goto label is a state

        Console.WriteLine("off.");             // State entry actions

        yield return null;                     // This means "Wait until a 
                                               // trigger is called"

                                               // Ah, we got triggered! 
                                               //   perform state exit actions 
                                               //   (none, in this case)

        if (Trigger == PressSwitch) goto on;   // Transitions go here: 
                                               // depending on the trigger 
                                               // that was called, go to
                                               // the right state

        InvalidTrigger();                      // Throw exception on 
                                               // invalid trigger

        ...

Isso funciona porque o compilador C # realmente criou uma máquina de estado internamente para cada método que usa yield return. Essa construção geralmente é usada para criar preguiçosamente sequências de dados, mas, neste caso, não estamos realmente interessados ​​na sequência retornada (que é nula de qualquer maneira), mas no comportamento do estado criado sob o capô.

A StateMachineclasse base faz alguma reflexão sobre a construção para atribuir código a cada [Trigger]ação, que define o Triggermembro e move a máquina de estado para frente.

Mas você realmente não precisa entender os internos para poder usá-lo.


2
O "goto" só é atroz se pular entre os métodos. Felizmente, isso não é permitido em C #.
Brannon

Bom ponto! Na verdade, eu ficaria muito impressionado se qualquer linguagem de tipo estaticamente conseguisse permitir um gotométodo entre.
Skrebbel #

3
@Brannon: qual idioma permite gotopular entre métodos? Não vejo como isso funcionaria. Não, gotoé problemático porque resulta em programação procedural (isso por si só complica coisas boas como teste de unidade), promove a repetição de código (percebeu como InvalidTriggerprecisa ser inserido para cada estado?) E, finalmente, torna o programa mais difícil de seguir. Compare isso com (a maioria) outras soluções neste segmento e você verá que este é o único onde todo o FSM acontece em um único método. Isso geralmente é suficiente para suscitar uma preocupação.
Groo

1
@Groo, GW-BASIC, por exemplo. Isso ajuda a não ter métodos ou funções. Além disso, tenho muita dificuldade em entender por que você acha o "fluxo do programa mais difícil de seguir" neste exemplo. É uma máquina de estado, "ir para" um estado de outro é a única coisa que você faz. Isso mapeia gotomuito bem.
Skrebbel

3
GW-BASIC permite gotosaltar entre funções, mas não suporta funções? :) Você está certo, a observação "mais difícil de seguir" é mais uma gotoquestão geral , na verdade não é um problema tão grande neste caso.
Groo

13

Você pode codificar um bloco iterador que permite executar um bloco de código de forma orquestrada. Como o bloco de código é dividido realmente não precisa corresponder a nada, é exatamente como você deseja codificá-lo. Por exemplo:

IEnumerable<int> CountToTen()
{
    System.Console.WriteLine("1");
    yield return 0;
    System.Console.WriteLine("2");
    System.Console.WriteLine("3");
    System.Console.WriteLine("4");
    yield return 0;
    System.Console.WriteLine("5");
    System.Console.WriteLine("6");
    System.Console.WriteLine("7");
    yield return 0;
    System.Console.WriteLine("8");
    yield return 0;
    System.Console.WriteLine("9");
    System.Console.WriteLine("10");
}

Nesse caso, quando você chama CountToTen, nada ainda é executado. O que você obtém é efetivamente um gerador de máquina de estado, para o qual você pode criar uma nova instância da máquina de estado. Você faz isso chamando GetEnumerator (). O IEnumerator resultante é efetivamente uma máquina de estado que você pode dirigir chamando MoveNext (...).

Portanto, neste exemplo, na primeira vez em que você chamar MoveNext (...), você verá "1" gravado no console e, na próxima vez em que chamar MoveNext (...), verá 2, 3, 4 e depois 5, 6, 7 e 8 e 9, 10. Como você pode ver, é um mecanismo útil para orquestrar como as coisas devem ocorrer.


6
Link obrigatório para aviso justo
sehe

8

Estou postando outra resposta aqui, pois são máquinas de estado de uma perspectiva diferente; muito visual.

Minha resposta original é o código imperativo clássico. Eu acho que é bastante visual como o código ocorre por causa da matriz que simplifica a visualização da máquina de estado. A desvantagem é que você deve escrever tudo isso. A resposta de Remos alivia o esforço de escrever o código da placa da caldeira, mas é muito menos visual. Existe a terceira alternativa; realmente desenhando a máquina de estado.

Se você estiver usando o .NET e puder direcionar a versão 4 do tempo de execução, terá a opção de usar as atividades da máquina de estado do fluxo de trabalho . Essencialmente, eles permitem desenhar a máquina de estados (como no diagrama de Juliet ) e fazer com que o tempo de execução do WF a execute para você.

Consulte o artigo MSDN Criando máquinas de estado com o Windows Workflow Foundation para obter mais detalhes e este site do CodePlex para a versão mais recente.

Essa é a opção que eu sempre preferiria quando visasse o .NET, porque é fácil de ver, alterar e explicar para não programadores; as imagens valem mais que mil palavras, como se costuma dizer!


Acho que a máquina de estado é uma das melhores partes de toda a base do fluxo de trabalho!
precisa saber é

7

É útil lembrar que as máquinas de estado são uma abstração e você não precisa de ferramentas específicas para criar uma, mas as ferramentas podem ser úteis.

Você pode, por exemplo, realizar uma máquina de estados com funções:

void Hunt(IList<Gull> gulls)
{
    if (gulls.Empty())
       return;

    var target = gulls.First();
    TargetAcquired(target, gulls);
}

void TargetAcquired(Gull target, IList<Gull> gulls)
{
    var balloon = new WaterBalloon(weightKg: 20);

    this.Cannon.Fire(balloon);

    if (balloon.Hit)
    {
       TargetHit(target, gulls);
    }
    else
       TargetMissed(target, gulls);
}

void TargetHit(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("Suck on it {0}!", target.Name);
    Hunt(gulls);
}

void TargetMissed(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("I'll get ya!");
    TargetAcquired(target, gulls);
}

Essa máquina caçava gaivotas e tentava atingi-las com balões de água. Se errar, ele tentará disparar um até atingir (o que poderia acontecer com algumas expectativas realistas;)); caso contrário, ele se alegrará no console. Ele continua a caçar até ficar sem gaivotas para assediar.

Cada função corresponde a cada estado; os estados inicial e final (ou aceito ) não são mostrados. Provavelmente existem mais estados do que modelados pelas funções. Por exemplo, depois de disparar o balão, a máquina está realmente em outro estado do que estava antes, mas decidi que essa distinção era impraticável.

Uma maneira comum é usar classes para representar estados e conectá-las de maneiras diferentes.


7

Encontrei este ótimo tutorial on-line e me ajudou a entender melhor as máquinas de estado finito.

http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867

O tutorial é independente da linguagem, portanto pode ser facilmente adaptado às suas necessidades de C #.

Além disso, o exemplo usado (uma formiga à procura de comida) é fácil de entender.


Do tutorial:

insira a descrição da imagem aqui

public class FSM {
    private var activeState :Function; // points to the currently active state function

    public function FSM() {
    }

    public function setState(state :Function) :void {
        activeState = state;
    }

    public function update() :void {
        if (activeState != null) {
            activeState();
        }
    }
}


public class Ant
{
    public var position   :Vector3D;
    public var velocity   :Vector3D;
    public var brain      :FSM;

    public function Ant(posX :Number, posY :Number) {
        position    = new Vector3D(posX, posY);
        velocity    = new Vector3D( -1, -1);
        brain       = new FSM();

        // Tell the brain to start looking for the leaf.
        brain.setState(findLeaf);
    }

    /**
    * The "findLeaf" state.
    * It makes the ant move towards the leaf.
    */
    public function findLeaf() :void {
        // Move the ant towards the leaf.
        velocity = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y);

        if (distance(Game.instance.leaf, this) <= 10) {
            // The ant is extremelly close to the leaf, it's time
            // to go home.
            brain.setState(goHome);
        }

        if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
            // Mouse cursor is threatening us. Let's run away!
            // It will make the brain start calling runAway() from
            // now on.
            brain.setState(runAway);
        }
    }

    /**
    * The "goHome" state.
    * It makes the ant move towards its home.
    */
    public function goHome() :void {
        // Move the ant towards home
        velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y);

        if (distance(Game.instance.home, this) <= 10) {
            // The ant is home, let's find the leaf again.
            brain.setState(findLeaf);
        }
    }

    /**
    * The "runAway" state.
    * It makes the ant run away from the mouse cursor.
    */
    public function runAway() :void {
        // Move the ant away from the mouse cursor
        velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y);

        // Is the mouse cursor still close?
        if (distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) {
            // No, the mouse cursor has gone away. Let's go back looking for the leaf.
            brain.setState(findLeaf);
        }
    }

    public function update():void {
        // Update the FSM controlling the "brain". It will invoke the currently
        // active state function: findLeaf(), goHome() or runAway().
        brain.update();

        // Apply the velocity vector to the position, making the ant move.
        moveBasedOnVelocity();
    }

    (...)
}

1
Embora esse link possa responder à pergunta, é melhor incluir aqui as partes essenciais da resposta e fornecer o link para referência. As respostas somente para links podem se tornar inválidas se a página vinculada for alterada. - Do comentário
drneel

@drneel Eu poderia copiar e colar bits do tutorial ... mas isso não tiraria crédito do autor?
Jet Blue

1
@ JetBlue: deixe o link na resposta como referência e inclua os bits relevantes em suas próprias palavras no post de resposta para não violar os direitos autorais de ninguém. Eu sei que parece rigoroso, mas muitas respostas se tornaram muito, muito melhores por causa dessa regra.
Flimm

6

Hoje eu estou profundamente envolvido no Design de Estado. Fiz e testei o ThreadState, que é igual a (+/-) a Threading em C #, conforme descrito na figura em Threading em C #

insira a descrição da imagem aqui

Você pode facilmente adicionar novos estados, configurar movimentos de um estado para outro é muito fácil, pois está incluído na implementação do estado

Implementação e uso em: Implementa .NET ThreadState por State Design Pattern


1
Link está morto. Você tem outra?
rola

5

Ainda não tentei implementar um FSM em C #, mas tudo isso soa (ou parece) muito complicado pela maneira como lidei com FSM no passado em linguagens de baixo nível como C ou ASM.

Eu acredito que o método que eu sempre conheci se chama algo como "Loop Iterativo". Nele, você basicamente possui um loop 'while' que sai periodicamente com base em eventos (interrupções) e depois retorna ao loop principal novamente.

Dentro dos manipuladores de interrupção, você passaria um CurrentState e retornaria um NextState, que substitui a variável CurrentState no loop principal. Você faz isso ad infinitum até o programa fechar (ou o microcontrolador redefinir).

O que estou vendo outras respostas parece muito complicado em comparação com a forma como um FSM é, na minha opinião, destinado a ser implementado; sua beleza reside em sua simplicidade e o FSM pode ser muito complicado com muitos estados e transições, mas eles permitem que processos complicados sejam facilmente decompostos e digeridos.

Sei que minha resposta não deve incluir outra pergunta, mas sou forçado a perguntar: por que essas outras soluções propostas parecem ser tão complicadas?
Eles parecem assemelhar-se a um pequeno prego com uma marreta gigante.


1
Totalmente de acordo. Um loop while simples com uma instrução switch é o mais simples possível.
rola

2
A menos que você tenha uma máquina de estados muito complicada com muitos estados e condições, onde você terminaria com várias opções aninhadas. Também pode haver uma penalidade na espera ocupada, dependendo da implementação do loop.
Sune Rievers

3

Que tal StatePattern. Isso atende às suas necessidades?

Eu acho que seu contexto está relacionado, mas vale a pena tentar com certeza.

http://en.wikipedia.org/wiki/State_pattern

Isso permite que seus estados decidam para onde ir e não a classe "objeto".

Bruno


1
O padrão de estado lida com uma classe que pode agir diferentemente com base no estado / modo em que está, não lida com a transição entre estados.
Eli Algranti

3

Na minha opinião, uma máquina de estados não serve apenas para alterar estados, mas também (muito importante) para manipular gatilhos / eventos dentro de um estado específico. Se você deseja entender melhor o padrão de projeto da máquina de estado, uma boa descrição pode ser encontrada no livro Padrões de design do primeiro cabeçote, página 320 .

Não é apenas sobre os estados nas variáveis, mas também sobre o manuseio de gatilhos nos diferentes estados. Ótimo capítulo (e não, não há nenhuma taxa para mim ao mencionar isso :-), que contém apenas uma explicação fácil de entender.


3

Eu acabei de contribuir com isso:

https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC

Aqui está um dos exemplos de demonstração do envio direto e indireto de comandos, com estados como IObserver (de sinal), respondendo assim a uma fonte de sinal, IObservable (de sinal):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    using Machines;

    public static class WatchingTvSampleAdvanced
    {
        // Enum type for the transition triggers (instead of System.String) :
        public enum TvOperation { Plug, SwitchOn, SwitchOff, Unplug, Dispose }

        // The state machine class type is also used as the type for its possible states constants :
        public class Television : NamedState<Television, TvOperation, DateTime>
        {
            // Declare all the possible states constants :
            public static readonly Television Unplugged = new Television("(Unplugged TV)");
            public static readonly Television Off = new Television("(TV Off)");
            public static readonly Television On = new Television("(TV On)");
            public static readonly Television Disposed = new Television("(Disposed TV)");

            // For convenience, enter the default start state when the parameterless constructor executes :
            public Television() : this(Television.Unplugged) { }

            // To create a state machine instance, with a given start state :
            private Television(Television value) : this(null, value) { }

            // To create a possible state constant :
            private Television(string moniker) : this(moniker, null) { }

            private Television(string moniker, Television value)
            {
                if (moniker == null)
                {
                    // Build the state graph programmatically
                    // (instead of declaratively via custom attributes) :
                    Handler<Television, TvOperation, DateTime> stateChangeHandler = StateChange;
                    Build
                    (
                        new[]
                        {
                            new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = stateChangeHandler },
                            new { From = Television.Unplugged, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.SwitchOn, Goto = Television.On, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.SwitchOff, Goto = Television.Off, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler }
                        },
                        false
                    );
                }
                else
                    // Name the state constant :
                    Moniker = moniker;
                Start(value ?? this);
            }

            // Because the states' value domain is a reference type, disallow the null value for any start state value : 
            protected override void OnStart(Television value)
            {
                if (value == null)
                    throw new ArgumentNullException("value", "cannot be null");
            }

            // When reaching a final state, unsubscribe from all the signal source(s), if any :
            protected override void OnComplete(bool stateComplete)
            {
                // Holds during all transitions into a final state
                // (i.e., stateComplete implies IsFinal) :
                System.Diagnostics.Debug.Assert(!stateComplete || IsFinal);

                if (stateComplete)
                    UnsubscribeFromAll();
            }

            // Executed before and after every state transition :
            private void StateChange(IState<Television> state, ExecutionStep step, Television value, TvOperation info, DateTime args)
            {
                // Holds during all possible transitions defined in the state graph
                // (i.e., (step equals ExecutionStep.LeaveState) implies (not state.IsFinal))
                System.Diagnostics.Debug.Assert((step != ExecutionStep.LeaveState) || !state.IsFinal);

                // Holds in instance (i.e., non-static) transition handlers like this one :
                System.Diagnostics.Debug.Assert(this == state);

                switch (step)
                {
                    case ExecutionStep.LeaveState:
                        var timeStamp = ((args != default(DateTime)) ? String.Format("\t\t(@ {0})", args) : String.Empty);
                        Console.WriteLine();
                        // 'value' is the state value that we are transitioning TO :
                        Console.WriteLine("\tLeave :\t{0} -- {1} -> {2}{3}", this, info, value, timeStamp);
                        break;
                    case ExecutionStep.EnterState:
                        // 'value' is the state value that we have transitioned FROM :
                        Console.WriteLine("\tEnter :\t{0} -- {1} -> {2}", value, info, this);
                        break;
                    default:
                        break;
                }
            }

            public override string ToString() { return (IsConstant ? Moniker : Value.ToString()); }
        }

        public static void Run()
        {
            Console.Clear();

            // Create a signal source instance (here, a.k.a. "remote control") that implements
            // IObservable<TvOperation> and IObservable<KeyValuePair<TvOperation, DateTime>> :
            var remote = new SignalSource<TvOperation, DateTime>();

            // Create a television state machine instance (automatically set in a default start state),
            // and make it subscribe to a compatible signal source, such as the remote control, precisely :
            var tv = new Television().Using(remote);
            bool done;

            // Always holds, assuming the call to Using(...) didn't throw an exception (in case of subscription failure) :
            System.Diagnostics.Debug.Assert(tv != null, "There's a bug somewhere: this message should never be displayed!");

            // As commonly done, we can trigger a transition directly on the state machine :
            tv.MoveNext(TvOperation.Plug, DateTime.Now);

            // Alternatively, we can also trigger transitions by emitting from the signal source / remote control
            // that the state machine subscribed to / is an observer of :
            remote.Emit(TvOperation.SwitchOn, DateTime.Now);
            remote.Emit(TvOperation.SwitchOff);
            remote.Emit(TvOperation.SwitchOn);
            remote.Emit(TvOperation.SwitchOff, DateTime.Now);

            done =
                (
                    tv.
                        MoveNext(TvOperation.Unplug).
                        MoveNext(TvOperation.Dispose) // MoveNext(...) returns null iff tv.IsFinal == true
                    == null
                );

            remote.Emit(TvOperation.Unplug); // Ignored by the state machine thanks to the OnComplete(...) override above

            Console.WriteLine();
            Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);

            Console.WriteLine();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }
}

Nota: este exemplo é bastante artificial e serve principalmente para demonstrar uma série de recursos ortogonais. Raramente deve haver uma necessidade real de implementar o próprio domínio de valor de estado por uma classe completa, usando o CRTP (consulte: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ) como este.

Aqui está um caso de uso de implementação certamente mais simples e provavelmente muito mais comum (usando um tipo de enum simples como o domínio de valor de estados), para a mesma máquina de estado e com o mesmo caso de teste:

https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    using Machines;

    public static class WatchingTvSample
    {
        public enum Status { Unplugged, Off, On, Disposed }

        public class DeviceTransitionAttribute : TransitionAttribute
        {
            public Status From { get; set; }
            public string When { get; set; }
            public Status Goto { get; set; }
            public object With { get; set; }
        }

        // State<Status> is a shortcut for / derived from State<Status, string>,
        // which in turn is a shortcut for / derived from State<Status, string, object> :
        public class Device : State<Status>
        {
            // Executed before and after every state transition :
            protected override void OnChange(ExecutionStep step, Status value, string info, object args)
            {
                if (step == ExecutionStep.EnterState)
                {
                    // 'value' is the state value that we have transitioned FROM :
                    Console.WriteLine("\t{0} -- {1} -> {2}", value, info, this);
                }
            }

            public override string ToString() { return Value.ToString(); }
        }

        // Since 'Device' has no state graph of its own, define one for derived 'Television' :
        [DeviceTransition(From = Status.Unplugged, When = "Plug", Goto = Status.Off)]
        [DeviceTransition(From = Status.Unplugged, When = "Dispose", Goto = Status.Disposed)]
        [DeviceTransition(From = Status.Off, When = "Switch On", Goto = Status.On)]
        [DeviceTransition(From = Status.Off, When = "Unplug", Goto = Status.Unplugged)]
        [DeviceTransition(From = Status.Off, When = "Dispose", Goto = Status.Disposed)]
        [DeviceTransition(From = Status.On, When = "Switch Off", Goto = Status.Off)]
        [DeviceTransition(From = Status.On, When = "Unplug", Goto = Status.Unplugged)]
        [DeviceTransition(From = Status.On, When = "Dispose", Goto = Status.Disposed)]
        public class Television : Device { }

        public static void Run()
        {
            Console.Clear();

            // Create a television state machine instance, and return it, set in some start state :
            var tv = new Television().Start(Status.Unplugged);
            bool done;

            // Holds iff the chosen start state isn't a final state :
            System.Diagnostics.Debug.Assert(tv != null, "The chosen start state is a final state!");

            // Trigger some state transitions with no arguments
            // ('args' is ignored by this state machine's OnChange(...), anyway) :
            done =
                (
                    tv.
                        MoveNext("Plug").
                        MoveNext("Switch On").
                        MoveNext("Switch Off").
                        MoveNext("Switch On").
                        MoveNext("Switch Off").
                        MoveNext("Unplug").
                        MoveNext("Dispose") // MoveNext(...) returns null iff tv.IsFinal == true
                    == null
                );

            Console.WriteLine();
            Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);

            Console.WriteLine();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }
}

«HTH


Não é um pouco estranho que cada instância de estado tenha sua própria cópia do gráfico de estado?
Groo

@Groo: não, eles não. Somente as instâncias do Television construídas usando o construtor privado com uma string nula para o apelido (daí chamar o método 'Build' protegido) terão um gráfico de estado, como máquinas de estado. Os outros, instâncias nomeadas de Televisão (com um apelido não nulo para esse propósito convencional e ad-hoc) serão meros estados de "ponto fixo" (por assim dizer), servindo como constantes de estado (que os gráficos de estado de máquinas de estados reais farão referência como seus vértices). 'HTH,
YSharp

Ok, eu entendi. Enfim, IMHO, teria sido melhor se você incluísse algum código que realmente lide com essas transições. Dessa forma, serve apenas como exemplo de uso de uma interface não tão óbvia (IMHO) para sua biblioteca. Por exemplo, como é StateChangeresolvido? Através da reflexão? Isso é mesmo necessário?
Groo

1
@Groo: Boa observação. Na verdade, não é necessário refletir sobre o manipulador nesse primeiro exemplo, porque é feito programaticamente lá com precisão e pode ser estaticamente vinculado / verificado por tipo (ao contrário dos atributos personalizados). Portanto, este trabalho como esperado também: private Television(string moniker, Television value) { Handler<Television, TvOperation, DateTime> myHandler = StateChange; // (code omitted) new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = myHandler } }
YSharp

1
Obrigado pelo seu esforço!
Groo

3

Eu criei essa máquina de estado genérica a partir do código de Juliet. Está funcionando demais para mim.

Estes são os benefícios:

  • você pode criar uma nova máquina de estado no código com duas enumerações TStatee TCommand,
  • estrutura adicionada TransitionResult<TState>para ter mais controle sobre os resultados de saída dos [Try]GetNext()métodos
  • expondo classe aninhada StateTransition única através AddTransition(TState, TCommand, TState)tornando-se mais fácil trabalhar com ele

Código:

public class StateMachine<TState, TCommand>
    where TState : struct, IConvertible, IComparable
    where TCommand : struct, IConvertible, IComparable
{
    protected class StateTransition<TS, TC>
        where TS : struct, IConvertible, IComparable
        where TC : struct, IConvertible, IComparable
    {
        readonly TS CurrentState;
        readonly TC Command;

        public StateTransition(TS currentState, TC command)
        {
            if (!typeof(TS).IsEnum || !typeof(TC).IsEnum)
            {
                throw new ArgumentException("TS,TC must be an enumerated type");
            }

            CurrentState = currentState;
            Command = command;
        }

        public override int GetHashCode()
        {
            return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            StateTransition<TS, TC> other = obj as StateTransition<TS, TC>;
            return other != null
                && this.CurrentState.CompareTo(other.CurrentState) == 0
                && this.Command.CompareTo(other.Command) == 0;
        }
    }

    private Dictionary<StateTransition<TState, TCommand>, TState> transitions;
    public TState CurrentState { get; private set; }

    protected StateMachine(TState initialState)
    {
        if (!typeof(TState).IsEnum || !typeof(TCommand).IsEnum)
        {
            throw new ArgumentException("TState,TCommand must be an enumerated type");
        }

        CurrentState = initialState;
        transitions = new Dictionary<StateTransition<TState, TCommand>, TState>();
    }

    /// <summary>
    /// Defines a new transition inside this state machine
    /// </summary>
    /// <param name="start">source state</param>
    /// <param name="command">transition condition</param>
    /// <param name="end">destination state</param>
    protected void AddTransition(TState start, TCommand command, TState end)
    {
        transitions.Add(new StateTransition<TState, TCommand>(start, command), end);
    }

    public TransitionResult<TState> TryGetNext(TCommand command)
    {
        StateTransition<TState, TCommand> transition = new StateTransition<TState, TCommand>(CurrentState, command);
        TState nextState;
        if (transitions.TryGetValue(transition, out nextState))
            return new TransitionResult<TState>(nextState, true);
        else
            return new TransitionResult<TState>(CurrentState, false);
    }

    public TransitionResult<TState> MoveNext(TCommand command)
    {
        var result = TryGetNext(command);
        if(result.IsValid)
        {
            //changes state
            CurrentState = result.NewState;
        }
        return result;
    }
}

Este é o tipo de retorno do método TryGetNext:

public struct TransitionResult<TState>
{
    public TransitionResult(TState newState, bool isValid)
    {
        NewState = newState;
        IsValid = isValid;
    }
    public TState NewState;
    public bool IsValid;
}

Como usar:

É assim que você pode criar a OnlineDiscountStateMachinepartir da classe genérica:

Defina uma enumeração OnlineDiscountStatepara seus estados e uma enumeração OnlineDiscountCommandpara seus comandos.

Definir uma classe OnlineDiscountStateMachinederivada da classe genérica usando essas duas enumerações

Derive o construtor base(OnlineDiscountState.InitialState)para que o estado inicial seja definido comoOnlineDiscountState.InitialState

Use AddTransitionquantas vezes for necessário

public class OnlineDiscountStateMachine : StateMachine<OnlineDiscountState, OnlineDiscountCommand>
{
    public OnlineDiscountStateMachine() : base(OnlineDiscountState.Disconnected)
    {
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Connected);
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Error_AuthenticationError);
        AddTransition(OnlineDiscountState.Connected, OnlineDiscountCommand.Submit, OnlineDiscountState.WaitingForResponse);
        AddTransition(OnlineDiscountState.WaitingForResponse, OnlineDiscountCommand.DataReceived, OnlineDiscountState.Disconnected);
    }
}

use a máquina de estados derivada

    odsm = new OnlineDiscountStateMachine();
    public void Connect()
    {
        var result = odsm.TryGetNext(OnlineDiscountCommand.Connect);

        //is result valid?
        if (!result.IsValid)
            //if this happens you need to add transitions to the state machine
            //in this case result.NewState is the same as before
            Console.WriteLine("cannot navigate from this state using OnlineDiscountCommand.Connect");

        //the transition was successfull
        //show messages for new states
        else if(result.NewState == OnlineDiscountState.Error_AuthenticationError)
            Console.WriteLine("invalid user/pass");
        else if(result.NewState == OnlineDiscountState.Connected)
            Console.WriteLine("Connected");
        else
            Console.WriteLine("not implemented transition result for " + result.NewState);
    }

1

Eu acho que a máquina de estado proposta por Juliet tem um erro: o método GetHashCode pode retornar o mesmo código de hash para duas transições diferentes, por exemplo:

Estado = Ativo (1), Comando = Pausa (2) => HashCode = 17 + 31 + 62 = 110

Estado = Pausado (2), Comando = Final (1) => HashCode = 17 + 62 + 31 = 110

Para evitar esse erro, o método deve ser assim:

public override int GetHashCode()
   {
            return 17 + 23 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
   }

Alex


1
O código de hash não é necessário para retornar um número exclusivo para qualquer combinação possível, apenas um valor distinto com boa distribuição no intervalo de destino (nesse caso, o intervalo é o conjunto de todos os intvalores possíveis ). É por isso que HashCodeé sempre implementado junto com Equals. Se os códigos de hash forem iguais, os objetos serão verificados quanto à exatidão exata usando o Equalsmétodo
Dmitry Avtonomov 13/07/19

0

FiniteStateMachine é uma Máquina de Estado Simples, escrita em C # Link

Vantagens de usar minha biblioteca FiniteStateMachine:

  1. Defina uma classe "context" para apresentar uma única interface para o mundo externo.
  2. Defina uma classe base abstrata do estado.
  3. Represente os diferentes "estados" da máquina de estados como classes derivadas da classe base do Estado.
  4. Defina o comportamento específico do estado nas classes derivadas apropriadas do estado.
  5. Mantenha um ponteiro para o "estado" atual na classe "contexto".
  6. Para alterar o estado da máquina de estado, altere o ponteiro "estado" atual.

Baixar DLL Download

Exemplo no LINQPad:

void Main()
{
            var machine = new SFM.Machine(new StatePaused());
            var output = machine.Command("Input_Start", Command.Start);
            Console.WriteLine(Command.Start.ToString() + "->  State: " + machine.Current);
            Console.WriteLine(output);

            output = machine.Command("Input_Pause", Command.Pause);
            Console.WriteLine(Command.Pause.ToString() + "->  State: " + machine.Current);
            Console.WriteLine(output);
            Console.WriteLine("-------------------------------------------------");
}
    public enum Command
    {
        Start,
        Pause,
    }

    public class StateActive : SFM.State
    {

        public override void Handle(SFM.IContext context)

        {
            //Gestione parametri
            var input = (String)context.Input;
            context.Output = input;

            //Gestione Navigazione
            if ((Command)context.Command == Command.Pause) context.Next = new StatePaused();
            if ((Command)context.Command == Command.Start) context.Next = this;

        }
    }


public class StatePaused : SFM.State
{

     public override void Handle(SFM.IContext context)

     {

         //Gestione parametri
         var input = (String)context.Input;
         context.Output = input;

         //Gestione Navigazione
         if ((Command)context.Command == Command.Start) context.Next = new  StateActive();
         if ((Command)context.Command == Command.Pause) context.Next = this;


     }

 }

1
Possui licença GNU GPL.
Der_Meister

0

Eu recomendaria state.cs . Eu pessoalmente usei state.js (a versão JavaScript) e estou muito feliz com isso. Essa versão C # funciona de maneira semelhante.

Você instancia estados:

        // create the state machine
        var player = new StateMachine<State>( "player" );

        // create some states
        var initial = player.CreatePseudoState( "initial", PseudoStateKind.Initial );
        var operational = player.CreateCompositeState( "operational" );
        ...

Você instancia algumas transições:

        var t0 = player.CreateTransition( initial, operational );
        player.CreateTransition( history, stopped );
        player.CreateTransition<String>( stopped, running, ( state, command ) => command.Equals( "play" ) );
        player.CreateTransition<String>( active, stopped, ( state, command ) => command.Equals( "stop" ) );

Você define ações em estados e transições:

    t0.Effect += DisengageHead;
    t0.Effect += StopMotor;

E é isso (praticamente). Consulte o site para obter mais informações.



0

Outra alternativa neste repositório https://github.com/lingkodsoft/StateBliss usou sintaxe fluente, suporta gatilhos.

    public class BasicTests
    {
        [Fact]
        public void Tests()
        {
            // Arrange
            StateMachineManager.Register(new [] { typeof(BasicTests).Assembly }); //Register at bootstrap of your application, i.e. Startup
            var currentState = AuthenticationState.Unauthenticated;
            var nextState = AuthenticationState.Authenticated;
            var data = new Dictionary<string, object>();

            // Act
            var changeInfo = StateMachineManager.Trigger(currentState, nextState, data);

            // Assert
            Assert.True(changeInfo.StateChangedSucceeded);
            Assert.Equal("ChangingHandler1", changeInfo.Data["key1"]);
            Assert.Equal("ChangingHandler2", changeInfo.Data["key2"]);
        }

        //this class gets regitered automatically by calling StateMachineManager.Register
        public class AuthenticationStateDefinition : StateDefinition<AuthenticationState>
        {
            public override void Define(IStateFromBuilder<AuthenticationState> builder)
            {
                builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                    .Changing(this, a => a.ChangingHandler1)
                    .Changed(this, a => a.ChangedHandler1);

                builder.OnEntering(AuthenticationState.Authenticated, this, a => a.OnEnteringHandler1);
                builder.OnEntered(AuthenticationState.Authenticated, this, a => a.OnEnteredHandler1);

                builder.OnExiting(AuthenticationState.Unauthenticated, this, a => a.OnExitingHandler1);
                builder.OnExited(AuthenticationState.Authenticated, this, a => a.OnExitedHandler1);

                builder.OnEditing(AuthenticationState.Authenticated, this, a => a.OnEditingHandler1);
                builder.OnEdited(AuthenticationState.Authenticated, this, a => a.OnEditedHandler1);

                builder.ThrowExceptionWhenDiscontinued = true;
            }

            private void ChangingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                var data = changeinfo.DataAs<Dictionary<string, object>>();
                data["key1"] = "ChangingHandler1";
            }

            private void OnEnteringHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                // changeinfo.Continue = false; //this will prevent changing the state
            }

            private void OnEditedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnExitedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnEnteredHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnEditingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
            }

            private void OnExitingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
            }

            private void ChangedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {
            }
        }

        public class AnotherAuthenticationStateDefinition : StateDefinition<AuthenticationState>
        {
            public override void Define(IStateFromBuilder<AuthenticationState> builder)
            {
                builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                    .Changing(this, a => a.ChangingHandler2);

            }

            private void ChangingHandler2(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                var data = changeinfo.DataAs<Dictionary<string, object>>();
                data["key2"] = "ChangingHandler2";
            }
        }
    }

    public enum AuthenticationState
    {
        Unauthenticated,
        Authenticated
    }
}

0

Você pode usar minha solução, esta é a maneira mais conveniente. Também é grátis.

Crie uma máquina de estados em três etapas:

1. Crie um esquema no editor de nós🔗 e carregue-o no seu projeto usando a biblioteca📚

StateMachine stateMachine = new StateMachine ("schema.xml");

2. Descreva a lógica do seu aplicativo em eventos⚡

stateMachine.GetState ("State1"). OnExit (Action1);
stateMachine.GetState ("State2"). OnEntry (Action2);
stateMachine.GetTransition ("Transition1"). OnInvoke (Action3);
stateMachine.OnChangeState (Action4);

3. Execute a máquina de estado🚘

stateMachine.Start ();

Ligações:

Editor de nó: https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor

Biblioteca: https://github.com/SimpleStateMachine/SimpleStateMachineLibrary

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.