Простой пример конечного автомата в C #?

259

Обновить:

Еще раз спасибо за примеры, они были очень полезны, и со следующим я не хочу ничего от них отнимать.

Разве приведенные в настоящее время примеры, насколько я понимаю их и конечных автоматов, не являются лишь половиной того, что мы обычно понимаем под конечным автоматом?
В том смысле, что примеры действительно меняют состояние, но это представлено только изменением значения переменной (и разрешением разных изменений значения в разных состояниях), в то время как обычно конечный автомат также должен изменять свое поведение, а поведение не (только) в смысл разрешения различных изменений значений для переменной в зависимости от состояния, но в смысле разрешения выполнения различных методов для разных состояний.

Или у меня неправильное представление о конечных автоматах и ​​их общем использовании?

С уважением


Оригинальный вопрос:

Я нашел это обсуждение о конечных автоматах и ​​блоках итераторов в c # и инструментах для создания конечных автоматов, а не о C #, так что я нашел много абстрактных вещей, но в качестве нуба все это немного сбивает с толку.

Так что было бы здорово, если бы кто-то мог предоставить пример исходного кода C #, который реализует простой конечный автомат, возможно, с 3,4 состояниями, просто чтобы понять его суть.


Дженнифер Оуэнс
источник
Вы задаетесь вопросом о конечных автоматах вообще или просто на основе итераторов?
Скурмедель
2
Существует .Net Core Stateless lib с примерами, DAG-схемами и т. Д., Которые стоит посмотреть
zmische

Ответы:

417

Давайте начнем с этой простой диаграммы состояний:

простая схема конечного автомата

У нас есть:

  • 4 состояния (неактивно, активно, приостановлено и вышло)
  • 5 типов переходов между состояниями (Начальная команда, Конечная команда, Команда паузы, Команда возобновления, Команда выхода).

Вы можете преобразовать это в C # несколькими способами, такими как выполнение оператора switch для текущего состояния и команды или поиск переходов в таблице переходов. Для этого простого конечного автомата я предпочитаю таблицу переходов, которую очень легко представить с помощью 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();
        }
    }
}

Что касается личных предпочтений, мне нравится проектировать мои конечные автоматы с GetNextфункцией для детерминированного возврата следующего состояния и MoveNextфункцией для изменения конечного автомата.

Джульетта
источник
66
+1 за правильную реализацию GetHashCode()использования простых чисел.
ja72
13
Не могли бы вы объяснить мне цель GetHashCode ()?
Сиддхарт
14
@Siddharth: StateTransitionкласс используется в качестве ключа в словаре, и важно равенство ключей. Два разных экземпляра StateTransitionследует считать равными, если они представляют один и тот же переход (например, CurrentStateи Commandявляются одинаковыми). Для реализации равенства вы должны переопределить, Equalsа также GetHashCode. В частности, словарь будет использовать хеш-код, и два равных объекта должны возвращать один и тот же хеш-код. Вы также получите хорошую производительность, если не слишком много неравных объектов совместно используют один и тот же хэш-код, поэтому GetHashCodeон реализован так, как показано.
Мартин Ливерсэйдж
14
Хотя это, безусловно, дает вам конечный автомат (и правильную реализацию на C #), я чувствую, что все еще не хватает ответа на вопрос ОП об изменении поведения? В конце концов, он просто вычисляет состояния, но поведение, связанное с изменениями состояния, фактическим содержанием программы и обычно называемым событиями входа / выхода, все еще отсутствует.
Stijn
2
Если кому-то это понадобится: я настроил эту машину Тейта и использовал ее в своей игре на единство. Это доступно на git hub: github.com/MarcoMig/Finite-State-Machine-FSM
Max_Power89
73

Возможно, вы захотите использовать один из существующих конечных автоматов с открытым исходным кодом. Например, bbv.Common.StateMachine можно найти по адресу http://code.google.com/p/bbvcommon/wiki/StateMachine . Он имеет очень интуитивно понятный свободный синтаксис и множество функций, таких как действия входа / выхода, действия перехода, охранники, иерархическая, пассивная реализация (выполняется в потоке вызывающей стороны) и активная реализация (собственный поток, в котором работает fsm, события добавляются в очередь).

На примере Джульетта определение конечного автомата становится очень простым:

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);

Обновление : местоположение проекта перемещено по адресу : https://github.com/appccelerate/statemachine.

Ремо Глур
источник
4
Спасибо за ссылку на этот отличный конечный автомат с открытым исходным кодом. Могу я спросить, как мне узнать текущее состояние?
Рамазан Полат
3
Вы не можете и не должны. Состояние что-то нестабильное. Когда вы запрашиваете состояние, возможно, вы находитесь в середине перехода. Все действия должны быть выполнены в переходах, входе в состояние и выходе из состояния. Если вы действительно хотите иметь состояние, вы можете добавить локальное поле и назначить состояние в действии ввода.
Ремо Глур
4
Вопрос в том, что вам «нужно» и действительно ли вам нужно состояние SM или какое-то другое состояние. Например, если вам нужен некоторый отображаемый текст, то несколько указанных могут иметь одинаковый отображаемый текст, например, если подготовка к отправке имеет несколько вложенных состояний. В этом случае вы должны делать именно то, что вы собираетесь делать. Обновите отображаемый текст в нужных местах. Например, в ExecuteOnEntry. Если вам нужна дополнительная информация, задайте новый вопрос и точно сформулируйте свою проблему, поскольку она выходит за рамки темы здесь.
Ремо Глор
Хорошо, я задаю новый вопрос и жду от вас ответа. Потому что я не думаю, что кто-то другой решит эту проблему, так как у вас есть лучший ответ, но все же спрашивающий не принял. Я опубликую вопрос здесь. Спасибо.
Рамазан Полат
4
+1 за свободный и декларативный API. Это круто Кстати, код Google, кажется, устарел. Их новый сайт проекта находится на GitHub здесь
KFL
52

Вот пример очень классического конечного автомата, моделирующего очень упрощенное электронное устройство (например, телевизор)

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
для тех, кто плохо знаком с конечными автоматами, это отличный первый пример того, как нужно сначала намочить ноги.
PositiveGuy
2
Я новичок в государственных машинах и серьезно, это принесло мне свет - спасибо!
MC5
1
Мне понравилась эта реализация. Для тех, кто может наткнуться на это, небольшое «улучшение». В классе FSM я добавил private void DoNothing() {return;}и заменил все экземпляры null на this.DoNothing. Имеет приятный побочный эффект возврата текущего состояния.
Sethmo011
1
Мне интересно, есть ли причина для некоторых из этих имен. Когда я смотрю на это, моя первая интуиция - переименовать элементы Statesв Unpowered, Standby, On. Я рассуждаю так: если бы меня спросили, в каком состоянии находится мой телевизор, я бы сказал «Выкл», а не «Старт». Я тоже изменился StandbyWhenOnи StandbyWhenOffна TurnOnи TurnOff. Это делает код более интуитивно понятным, но мне интересно, существуют ли соглашения или другие факторы, которые делают мою терминологию менее подходящей.
Джейсон Хамье
Кажется разумным, я действительно не следовал никакому соглашению об именах штатов; имя, как имеет смысл для любой модели.
Пит Стенсен
20

Некоторая бесстыдная самореклама здесь, но некоторое время назад я создал библиотеку под названием YieldMachine, которая позволяет описать конечный автомат ограниченной сложности очень простым и понятным способом. Например, рассмотрим лампу:

конечный автомат лампы

Обратите внимание, что этот конечный автомат имеет 2 триггера и 3 состояния. В коде YieldMachine мы пишем единый метод для всего поведения, связанного с состоянием, в котором мы совершаем ужасное злодеяние использования gotoдля каждого состояния. Триггер становится свойством или полем типа Action, украшенным названным атрибутом Trigger. Я прокомментировал код первого состояния и его переходы ниже; следующие состояния следуют той же схеме.

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();
    }
}

Коротко и красиво, а!

Этот конечный автомат управляется простой отправкой ему триггеров:

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

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

Просто чтобы уточнить, я добавил несколько комментариев к первому состоянию, чтобы помочь вам понять, как это использовать.

    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

        ...

Это работает, потому что компилятор C # фактически создал конечный автомат для каждого метода, который использует yield return. Эта конструкция обычно используется для ленивого создания последовательностей данных, но в этом случае нас на самом деле не интересует возвращаемая последовательность (которая в любом случае равна нулю), а поведение поведения, которое создается внутри.

StateMachineБазовый класс делает некоторые размышления о строительстве для присвоения кода для каждого [Trigger]действия, которое устанавливает Triggerэлемент и перемещает государственную машину вперед.

Но вам не нужно понимать внутренности, чтобы иметь возможность использовать их.

skrebbel
источник
2
«Goto» является жестоким, только если он прыгает между методами. К счастью, это не разрешено в C #.
Браннон
Хорошая точка зрения! На самом деле, я был бы очень впечатлен, если бы какой-либо статически типизированный язык смог бы разрешить использование gotoмежду методами
скреббель
3
@Brannon: какой язык позволяет gotoпереходить между методами? Я не понимаю, как это могло бы сработать. Нет, gotoэто проблематично, потому что это приводит к процедурному программированию (это само по себе усложняет такие приятные вещи, как модульное тестирование), способствует повторению кода (замечено, как его InvalidTriggerнужно вставлять для каждого состояния?) И, наконец, усложняет выполнение программы. Сравните это с (большинством) другими решениями в этой теме, и вы увидите, что это единственное решение, в котором весь FSM выполняется одним способом. Этого обычно достаточно, чтобы вызвать беспокойство.
Groo
1
@ Groo, GW-BASIC, например. Помогает то, что у него нет методов или даже функций. Кроме того, мне очень трудно понять, почему в этом примере вам труднее следовать «потоку программ». Это конечный автомат, единственное, что вы делаете, - это переход к другому состоянию. Это gotoочень хорошо.
скреббель
3
GW-BASIC позволяет gotoпереключаться между функциями, но он не поддерживает функции? :) Вы правы, замечание "труднее следовать" - это более общая gotoпроблема, в данном случае это не такая уж большая проблема.
Groo
13

Вы можете кодировать блок итератора, который позволяет вам выполнить блок кода организованным способом. То, как блок кода разбит, на самом деле не должно соответствовать чему-либо, просто то, как вы хотите его кодировать. Например:

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");
}

В этом случае, когда вы вызываете CountToTen, на самом деле ничего еще не выполняется. По сути, вы получаете генератор конечных автоматов, для которого вы можете создать новый экземпляр конечного автомата. Вы делаете это, вызывая GetEnumerator (). Получающийся IEnumerator фактически является конечным автоматом, который вы можете использовать, вызывая MoveNext (...).

Таким образом, в этом примере при первом вызове MoveNext (...) вы увидите «1», записанное в консоль, а при следующем вызове MoveNext (...) вы увидите 2, 3, 4 и затем 5, 6, 7, а затем 8, а затем 9, 10. Как видите, это полезный механизм для организации того, как все должно происходить.

Кевин Хсу
источник
6
Обязательная ссылка на справедливое предупреждение
сехе
8

Я публикую здесь другой ответ, так как это конечные автоматы с другой точки зрения; очень наглядно

Мой оригинальный ответ - классический императивный код. Я думаю, что это довольно визуально, поскольку код идет из-за массива, который делает визуализацию конечного автомата простой. Недостатком является то, что вы должны написать все это. Ответ Ремоса облегчает написание кода, но гораздо менее нагляден. Есть третья альтернатива; действительно рисует конечный автомат.

Если вы используете .NET и можете использовать версию 4 среды выполнения, у вас есть возможность использовать действия конечного автомата рабочего процесса . По сути, они позволяют вам нарисовать конечный автомат (как на диаграмме Джульетты ) и заставить WF выполнить его за вас.

Для получения дополнительной информации см. Статью MSDN « Создание конечных автоматов с помощью Windows Workflow Foundation» и этот сайт CodePlex для последней версии.

Это вариант, который я бы всегда предпочел при ориентации на .NET, потому что его легко увидеть, изменить и объяснить не программистам; картинки стоят тысячи слов как говорится!

Пит Стенсонес
источник
Я думаю, что конечный автомат - одна из лучших частей всего рабочего процесса!
fabsenet
7

Полезно помнить, что конечные автоматы являются абстракцией, и вам не нужны специальные инструменты для их создания, однако инструменты могут быть полезны.

Например, вы можете реализовать конечный автомат с функциями:

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);
}

Эта машина охотилась на чаек и пыталась поразить их водяными шарами. Если он пропустит, он будет пытаться выстрелить до тех пор, пока не достигнет цели (может, с некоторыми реалистичными ожиданиями;)), иначе он будет злорадствовать в консоли. Это продолжает охотиться, пока это не из чаек, чтобы изводить.

Каждая функция соответствует каждому состоянию; состояния начала и окончания (или принятия ) не показаны. Там, вероятно, больше состояний, чем моделируется функциями. Например, после запуска воздушного шара машина действительно находится в другом состоянии, чем было до нее, но я решил, что сделать это различие нецелесообразно.

Обычный способ - использовать классы для представления состояний, а затем соединять их различными способами.

Skurmedel
источник
7

Нашел этот замечательный учебник в Интернете, и он помог мне обернуть голову в конечные автоматы.

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

Учебное пособие не зависит от языка, поэтому его можно легко адаптировать к вашим потребностям в C #.

Кроме того, используемый пример (муравей ищет еду) легко понять.


Из учебника:

введите описание изображения здесь

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();
    }

    (...)
}
Jet Blue
источник
1
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится. - Из обзора
drneel
@drneel Я мог бы копировать и вставлять фрагменты из учебника ... но разве это не отнимает у автора должное?
Jet Blue
1
@JetBlue: оставьте ссылку в ответе в качестве ссылки и включите соответствующие биты в свои слова в ответном сообщении, чтобы не нарушать чьи-либо авторские права. Я знаю, что это кажется строгим, но многие ответы стали намного лучше благодаря этому правилу.
Flimm
6

Сегодня я в глубине государства Design Pattern. Я сделал и протестировал ThreadState, который равен (+/-) Threading в C #, как описано на рисунке из Threading в C #

введите описание изображения здесь

Вы можете легко добавлять новые состояния, настраивать переходы из одного состояния в другое очень легко, потому что оно включено в реализацию состояний

Внедрение и использование в: Реализует .NET ThreadState с помощью State Design Pattern

zzfima
источник
2
Ссылка мертва. У вас есть другой?
катится
5

Я еще не пробовал внедрять FSM в C #, но все это звучит (или выглядит) очень сложно по сравнению с тем, как я работал с FSM в прошлом на языках низкого уровня, таких как C или ASM.

Я считаю, что метод, который я всегда знал, называется чем-то вроде «Итеративного цикла». В нем, по сути, имеется цикл while, который периодически выходит на основе событий (прерываний), а затем снова возвращается в основной цикл.

В обработчиках прерываний вы должны передать CurrentState и вернуть NextState, который затем перезаписывает переменную CurrentState в основном цикле. Вы делаете это до бесконечности, пока программа не закроется (или микроконтроллер не перезагрузится).

То, что я вижу, другие ответы выглядят очень сложными по сравнению с тем, как, на мой взгляд, ФШМ должен быть реализован; Его красота заключается в его простоте, и FSM может быть очень сложным со многими, многими состояниями и переходами, но они позволяют легко разбивать и переваривать сложный процесс.

Я понимаю, что мой ответ не должен включать другой вопрос, но я вынужден спросить: почему эти другие предлагаемые решения кажутся такими сложными?
Кажется, они похожи на попадание маленького гвоздя с помощью гигантской кувалды

dluberger
источник
1
Полностью согласен. Простой цикл while с оператором switch настолько прост, насколько это возможно.
катится
2
Если у вас нет очень сложного конечного автомата со многими состояниями и условиями, в котором вы бы получили несколько вложенных переключателей. Также может быть штраф в ожидании занятости, в зависимости от реализации вашего цикла.
Сьюн Риверс
3

Какой бой StatePattern. Это соответствует вашим потребностям?

Я думаю, что его контекст связан, но его точно стоит попробовать.

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

Это позволяет вашим штатам решать, куда идти, а не «объектному» классу.

Bruno

Бруно Бертечини
источник
1
Шаблон состояний имеет дело с классом, который может действовать по-разному в зависимости от состояния / режима, в котором он находится, он не имеет дело с переходом между состояниями.
Эли Альгранти
3

По моему мнению, конечный автомат предназначен не только для изменения состояний, но также (очень важно) для обработки триггеров / событий в определенном состоянии. Если вы хотите лучше понять шаблон проектирования конечного автомата, хорошее описание можно найти в книге Head First Design Patterns, стр. 320 .

Речь идет не только о состояниях внутри переменных, но и об обработке триггеров в различных состояниях. Отличная глава (и нет, упоминать об этом мне не нужно :-), которая содержит просто понятное объяснение.

Тон Сноей
источник
3

Я только что добавил это:

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

Вот один из примеров, демонстрирующих прямую и непрямую отправку команд с состояниями как IObserver (для сигнала), таким образом, отвечает на источник сигнала, IObservable (для сигнала):

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();
        }
    }
}

Примечание: этот пример довольно искусственный и в основном предназначен для демонстрации ряда ортогональных функций. Там должна быть редко реальная потребность в реализации самого состояния области значений полным взорван классом, используя CRTP (см http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ) , как это.

Вот для гораздо более простого и, вероятно, гораздо более распространенного варианта использования реализации (с использованием простого типа enum в качестве области значений состояний), для того же конечного автомата и с тем же тестовым примером:

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();
        }
    }
}

«НТН

YSharp
источник
Не странно ли, что каждый экземпляр состояния имеет свою собственную копию графа состояний?
Groo
@Groo: нет, они не делают. Только экземпляры Television, созданные с использованием закрытого конструктора с пустой строкой для моникера (следовательно, вызывающего защищенный метод Build), будут иметь в качестве конечных автоматов граф состояний. Другие, именованные экземпляры Television (с прозвищем, не равным NULL для этой обычной и специальной цели), будут просто состояниями «фиксированной точки» (так сказать), служа константами состояния (что граф (ы) состояний фактические конечные автоматы будут ссылаться как их вершины). 'HTH,
YSharp
Хорошо, я понимаю. В любом случае, ИМХО, было бы лучше, если бы вы включили некоторый код, который фактически обрабатывает эти переходы. Таким образом, он служит только примером использования не очень очевидного интерфейса (IMHO) для вашей библиотеки. Например, как это StateChangeрешается? Через отражение? Это действительно необходимо?
Groo
1
@ Гру: Хорошее замечание. На самом деле нет необходимости отражать обработчик в этом первом примере, потому что он выполняется там точно программно и может быть статически связан / проверен на тип (в отличие от пользовательских атрибутов). Так что эта работа, как ожидалось, тоже: 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
Спасибо за ваши усилия!
Groo
3

Я сделал этот универсальный конечный автомат из кода Джульетты. Это работает потрясающе для меня.

Вот эти преимущества:

  • Вы можете создать новый конечный автомат в коде с двумя перечислениями TStateи TCommand,
  • добавлена ​​структура TransitionResult<TState>для большего контроля над результатами вывода [Try]GetNext()методов
  • разоблачение вложенного класса StateTransition только за счет AddTransition(TState, TCommand, TState)упрощения работы с ним

Код:

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;
    }
}

Это тип возврата метода TryGetNext:

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

Как пользоваться:

Вот как вы можете создать OnlineDiscountStateMachineиз универсального класса:

Определите перечисление OnlineDiscountStateдля его состояний и перечисление OnlineDiscountCommandдля его команд.

Определите класс, OnlineDiscountStateMachineпроизводный от универсального класса, используя эти два перечисления

Получите конструктор, base(OnlineDiscountState.InitialState)чтобы начальное состояние было установленоOnlineDiscountState.InitialState

Используйте AddTransitionстолько раз, сколько необходимо

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);
    }
}

использовать производный конечный автомат

    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

Я думаю, что конечный автомат, предложенный Джульеттой, имеет ошибку: метод GetHashCode может возвращать один и тот же хэш-код для двух разных переходов, например:

Состояние = активный (1), команда = пауза (2) => HashCode = 17 + 31 + 62 = 110

State = Paused (2), Command = End (1) => HashCode = 17 + 62 + 31 = 110

Чтобы избежать этой ошибки, метод должен быть таким:

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

Alex

alexag
источник
1
Хеш-код не обязан возвращать уникальный номер для любой возможной комбинации, только отдельное значение с хорошим распределением по целевому диапазону (в этом случае диапазон - все возможные intзначения). Вот почему HashCodeвсегда реализуется вместе с Equals. Если хеш-коды одинаковы, то объекты проверяются на точную корректность с помощью Equalsметода.
Дмитрий Автономов
0

FiniteStateMachine - это простой конечный автомат, написанный на C # Link.

Преимущества использования моей библиотеки FiniteStateMachine:

  1. Определите класс «context» для представления единого интерфейса с внешним миром.
  2. Определите состояние абстрактного базового класса.
  3. Представлять различные «состояния» конечного автомата как производные классы базового класса State.
  4. Определите поведение, зависящее от состояния, в соответствующих классах, производных от состояния.
  5. Поддерживать указатель на текущее состояние в классе context.
  6. Чтобы изменить состояние конечного автомата, измените текущий указатель «состояния».

Скачать DLL Скачать

Пример на 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
Имеет лицензию GNU GPL.
Der_Meister
0

Я бы порекомендовал state.cs . Я лично использовал state.js (версия JavaScript) и очень доволен этим. Эта версия C # работает аналогичным образом.

Вы создаете экземпляры состояний:

        // 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" );
        ...

Вы создаете несколько переходов:

        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" ) );

Вы определяете действия над состояниями и переходами:

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

И это (в значительной степени) это. Посмотрите на сайте для получения дополнительной информации.

bmorin
источник
0

В NuGet есть 2 популярных пакета конечных автоматов.

Appccelerate.StateMachine (13.6K загрузок + 3.82K устаревшей версии (bbv.Common.StateMachine))

StateMachineToolkit (1.56K загрузок)

В библиотеке Appccelerate lib есть хорошая документация , но она не поддерживает .NET 4, поэтому я выбрал StateMachineToolkit для своего проекта.

Der_Meister
источник
0

Другая альтернатива в этом репо https://github.com/lingkodsoft/StateBliss использует свободный синтаксис, поддерживает триггеры.

    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
    }
}
mcdm
источник
0

Вы можете использовать мое решение, это наиболее удобный способ. Это также бесплатно.

Создайте конечный автомат в три этапа:

1. Создайте схему в редакторе узлов и загрузите ее в свой проект, используя библиотеку.

StateMachine stateMachine = новый StateMachine ("sche.xml");

2. Опишите логику своего приложения на событиях

stateMachine.GetState ( "State1") OnExit (Action1).
stateMachine.GetState ( "State2") OnEntry (Мотор2).
stateMachine.GetTransition ( "Transition1") OnInvoke (действий3).
stateMachine.OnChangeState (действий4);

3. Запустить конечный автомат🚘

stateMachine.Start ();

Ссылки:

Редактор узлов: https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor

Библиотека: https://github.com/SimpleStateMachine/SimpleStateMachineLibrary

GMIKE
источник