【问题标题】:Statemachine that transitions to target state and fires transitions and states between?转换到目标状态并触发转换和状态之间的状态机?
【发布时间】:2016-02-04 16:49:01
【问题描述】:

我最近使用了Stateless 状态机。我可以像这样定义转换等的规则:

stateMachine.Configure(State.Unknown)
    .Permit(Trigger.StartApplication, State.Initialized)
    .OnEntry(this.DoBeforeTransition)
    .OnExit(this.DoAfterTransition);

stateMachine.Configure(State.Initialized)
    .Permit(Trigger.CheckSomething, State.SomethingChecked)
    .OnEntry(this.DoBeforeTransition)
    .OnExit(this.DoAfterTransition);

然后您就可以触发触发器来更改状态。但是,如果您想进入特定状态,您需要知道当前状态以及下一个状态。因此,如果没有定义直接转换,状态机的“客户端”需要知道如何达到某个状态。是否有可能调用诸如“goto”之类的东西并且机器自己触发所有必需的触发器?

【问题讨论】:

    标签: .net state-machine stateless-state-machine


    【解决方案1】:

    您可以这样做,因为每个州只有一个“许可证”。如果您有多个“许可”,那么您将无法在工作流程中自动移动(必须有某些原因导致您选择一个许可/触发器而不是另一个)。当我说你“不能”时,这不是技术上的,而是实际上的。

    下面是一个在工作流程中自动移动的示例。

    using Stateless;
    using System;
    using System.Runtime.CompilerServices;
    
    namespace MyExample.BAL.WorkFlows
    {
        public class TelephoneCallWorkFlow
        {
    
            private static volatile StateMachine<TelephoneCallStateEnum, TelephoneCallTriggerEnum> SingletonInstance;
    
            public StateMachine<TelephoneCallStateEnum, TelephoneCallTriggerEnum> Instance
            {
                [MethodImpl(MethodImplOptions.Synchronized)]
                get
                {
                    if (SingletonInstance == null)
                    {
                        SingletonInstance = new StateMachine<TelephoneCallStateEnum, TelephoneCallTriggerEnum>(TelephoneCallStateEnum.OffHook);
    
                        SingletonInstance.Configure(TelephoneCallStateEnum.OffHook)
                            .Permit(TelephoneCallTriggerEnum.CallDialed, TelephoneCallStateEnum.Ringing);
    
                        SingletonInstance.Configure(TelephoneCallStateEnum.Ringing)
                            //removing so there is only one valid path workflow//.Permit(TelephoneCallTriggerEnum.HungUp, TelephoneCallStateEnum.OffHook)
                            .Permit(TelephoneCallTriggerEnum.CallConnected, TelephoneCallStateEnum.Connected);
    
                        SingletonInstance.Configure(TelephoneCallStateEnum.Connected)
                            //.OnEntry(t => StartCallTimer())
                            //.OnExit(t => StopCallTimer())
                            //removing so there is only one valid path workflow//.Permit(TelephoneCallTriggerEnum.LeftMessage, TelephoneCallStateEnum.OffHook)
                            //removing so there is only one valid path workflow//.Permit(TelephoneCallTriggerEnum.HungUp, TelephoneCallStateEnum.OffHook)
                            .Permit(TelephoneCallTriggerEnum.PlacedOnHold, TelephoneCallStateEnum.OnHold)
                            ;
    
                        SingletonInstance.Configure(TelephoneCallStateEnum.OnHold)
                            //removing so there is only one valid path workflow//.SubstateOf(TelephoneCallStateEnum.Connected)
                            //removing so there is only one valid path workflow//.Permit(TelephoneCallTriggerEnum.TakenOffHold, TelephoneCallStateEnum.Connected)
                            //removing so there is only one valid path workflow//.Permit(TelephoneCallTriggerEnum.HungUp, TelephoneCallStateEnum.OffHook)
                            .Permit(TelephoneCallTriggerEnum.PhoneHurledAgainstWall, TelephoneCallStateEnum.PhoneDestroyed)
                            ;
                    }
    
                    return SingletonInstance;
                }
            }
    
            public void Fire(TelephoneCallTriggerEnum trigger)
            {
                Console.WriteLine("............[Firing:] {0}", trigger);
                this.Instance.Fire(trigger);
            }
        }
    }
    
    public enum TelephoneCallStateEnum
    {
        OffHook,
        Ringing,
        Connected,
        OnHold,
        PhoneDestroyed
    }
    
    public enum TelephoneCallTriggerEnum
    {
        CallDialed,
        HungUp,
        CallConnected,
        LeftMessage,
        PlacedOnHold,
        TakenOffHold,
        PhoneHurledAgainstWall
    }
    

    现在是“自动移动”技巧。

                TelephoneCallWorkFlow tcwf1 = new TelephoneCallWorkFlow();
                IEnumerable<TelephoneCallTriggerEnum> myPermittedTriggers = tcwf1.Instance.PermittedTriggers;
                while (null != myPermittedTriggers && myPermittedTriggers.Count() > 0)
                {
                    if (myPermittedTriggers.Count() > 1)
                    {
                        throw new ArgumentOutOfRangeException("You cannot auto-move the workflow when there's more than one trigger");
                    }
                    TelephoneCallTriggerEnum nextTrigger = myPermittedTriggers.FirstOrDefault();
                    Console.WriteLine("About to call the 'next' trigger: --> {0}", nextTrigger);
                    tcwf1.Fire(nextTrigger);
                    Console.WriteLine("CurrentState: --> {0}", tcwf1.Instance.State);
                    myPermittedTriggers = tcwf1.Instance.PermittedTriggers;
                }
    

    您基本上获得了 PermittedTriggers,并获得了第一个(为了使自动移动工作,每个状态应该只有一个 Permitted-Trigger)......然后调用该触发器。

    同样,实际上(不是技术上)您只有在每个州有一个许可/触发器时才会这样做。因此,如果有超过 1 个,为什么我有一个例外。如果有超过 1 个,你可以“获得第一个”,这没有任何意义。

    【讨论】: