Jen*_*ens 244 c# state-machine
更新:
再次感谢这些例子,他们非常有帮助,以下我并不是要从他们身上拿走任何东西.
就我理解它们和状态机而言,目前给出的例子不是我们通常理解的状态机的一半吗?
在某种意义上,示例确实改变了状态,但这只是通过改变变量的值来表示(并允许在不同的状态中允许不同的值变化),而通常状态机也应该改变它的行为,并且行为不是(仅)在根据状态允许变量的不同值更改的意义,但是允许针对不同状态执行不同方法.
或者我对状态机及其常见用途存在误解?
最好的祝福
原始问题:
我在c#中找到了关于状态机和迭代器块的讨论以及创建状态机的工具以及C#没有的东西,所以我发现了许多抽象的东西,但作为一个菜鸟,所有这些都有点令人困惑.
因此,如果有人可以提供一个C#源代码示例,它可以实现一个简单的状态机,可能有3,4个状态,只是为了得到它的要点.
Jul*_*iet 398
让我们从这个简单的状态图开始:
我们有:
您可以通过多种方式将其转换为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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
作为个人喜好,我喜欢设计我的状态机,其GetNext功能是确定性地返回下一个状态,以及MoveNext改变状态机的功能.
Rem*_*oor 70
您可能希望使用现有的开源有限状态机之一.例如,在http://code.google.com/p/bbvcommon/wiki/StateMachine上找到了bbv.Common.StateMachine .它具有非常直观的流畅语法和许多功能,例如进入/退出操作,转换操作,警卫,分层,被动实现(在调用者的线程上执行)和活动实现(fsm运行的自己的线程,事件被添加到队列中).
以Juliets为例,状态机的定义变得非常简单:
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);
Run Code Online (Sandbox Code Playgroud)
更新:项目位置已移至:https://github.com/appccelerate/statemachine
Pet*_*nes 48
这是一个非常经典的有限状态机的例子,建模一个非常简化的电子设备(如电视)
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; }
}
}
}
Run Code Online (Sandbox Code Playgroud)
skr*_*bel 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();
}
}
Run Code Online (Sandbox Code Playgroud)
简短又好看,呃!
只需向其发送触发器即可控制此状态机:
var sm = new Lamp();
sm.PressSwitch(); //go on
sm.PressSwitch(); //go off
sm.PressSwitch(); //go on
sm.GotError(); //get error
sm.PressSwitch(); //go off
Run Code Online (Sandbox Code Playgroud)
为了澄清,我在第一个州添加了一些注释,以帮助您了解如何使用它.
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
...
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为C#编译器实际上为每个使用的方法在内部创建了一个状态机yield return.这个构造通常用于懒惰地创建数据序列,但在这种情况下,我们实际上并不对返回的序列感兴趣(无论如何都是null),而是在引擎盖下创建的状态行为.
所述StateMachine基类确实上建设一些反射来分配代码到每个[Trigger]动作,设定Trigger构件和所述状态机向前移动.
但是你真的不需要理解内部能够使用它.
Kev*_*Hsu 11
您可以编写迭代器块代码,以便以协调的方式执行代码块.代码块是如何分解的,实际上不必对应任何东西,它只是你想要编码的方式.例如:
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");
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,当您调用CountToTen时,实际上没有执行任何操作.你得到的实际上是一个状态机生成器,你可以为它创建一个状态机的新实例.您可以通过调用GetEnumerator()来完成此操作.生成的IEnumerator实际上是一个状态机,您可以通过调用MoveNext(...)来驱动它.
因此,在这个例子中,第一次调用MoveNext(...)时,您会看到"1"写入控制台,下次调用MoveNext(...)时,您将看到2,3,4和然后是5,6,7和8,然后是9,10.正如你所看到的,它是一种有用的机制,用于协调事情应该如何发生.
我在这里发布了另一个答案,因为这是来自不同角度的状态机; 非常直观.
我的原始答案是clasic不完整的代码.我认为它的代码非常直观,因为数组使得状态机的可视化变得简单.缺点是你必须写下这一切. Remos的回答缓解了编写样板代码的努力,但远没有那么直观.还有第三种选择; 真的画国家机器.
如果您使用的是.NET并且可以定位运行时的第4版,那么您可以选择使用工作流的状态机活动.这些本质上是让你绘制状态机(就像朱丽叶的图中一样)并让WF运行时为你执行它.
有关更多详细信息,请参阅MSDN文章使用Windows Workflow Foundation构建状态机,以及此CodePlex站点获取最新版本.
这是我在定位.NET时总是喜欢的选项,因为它很容易看到,改变并向非程序员解释; 他们说的图片胜过千言万语!
记住状态机是一个抽象是很有用的,你不需要特殊的工具来创建一个,但是工具可能很有用.
例如,您可以实现具有以下功能的状态机:
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);
}
Run Code Online (Sandbox Code Playgroud)
这台机器会搜寻海鸥并试图用水气球打它们.如果它未命中它将尝试触发它直到它击中(可以做一些现实的期望;)),否则它将在控制台中幸灾乐祸.它继续捕猎,直到它被海鸥骚扰.
每个功能对应于每个状态; 未显示开始和结束(或接受)状态.那里可能有更多的状态,而不是由函数建模.例如,在点燃气球之后,机器真的处于与之前相比的另一种状态,但我认为这种区别是不切实际的.
一种常见的方法是使用类来表示状态,然后以不同的方式连接它们.
今天我深入国家设计模式.我做了并测试了ThreadState,它与C#中的线程相等(+/-),如C#中的线程图所示

您可以轻松添加新状态,配置从一个状态到另一个状态的移动非常容易,因为它在状态实现中封装
实现和使用at:通过State Design Pattern实现.NET ThreadState
在网上找到这个很棒的教程,它帮助我绕过有限状态机.
该教程与语言无关,因此可以轻松地适应您的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();
}
(...)
}
Run Code Online (Sandbox Code Playgroud)
不确定我是否错过了重点,但我认为这里的答案都不是“简单”的状态机。我通常所说的简单状态机是使用内部带有开关的循环。这就是我们在大学的PLC/微芯片编程或C/C++编程中使用的方法。
优点:
缺点:
看起来像这样:
public enum State
{
First,
Second,
Third,
}
static void Main(string[] args)
{
var state = State.First;
// x and i are just examples for stuff that you could change inside the state and use for state transitions
var x = 0;
var i = 0;
// does not have to be a while loop. you could loop over the characters of a string too
while (true)
{
switch (state)
{
case State.First:
// Do sth here
if (x == 2)
state = State.Second;
// you may or may not add a break; right after setting the next state
// or do sth here
if (i == 3)
state = State.Third;
// or here
break;
case State.Second:
// Do sth here
if (x == 10)
state = State.First;
// or do sth here
break;
case State.Third:
// Do sth here
if (x == 10)
state = State.First;
// or do sth here
break;
default:
// you may wanna throw an exception here.
break;
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果它确实应该是一个状态机,您可以在其中调用根据您所处的状态做出不同反应的方法:状态设计模式是更好的方法
小智 5
我还没有尝试在C#中实现FSM,但这些听起来(或看起来)非常复杂,因为我过去在C或ASM等低级语言中处理FSM的方式.
我相信我一直都知道的方法被称为"迭代循环".在它中,你基本上有一个'while'循环,它根据事件(中断)定期退出,然后再次返回主循环.
在中断处理程序中,您将传递一个CurrentState并返回一个NextState,然后它将覆盖主循环中的CurrentState变量.在程序关闭(或微控制器重置)之前,您无限制地执行此操作.
我所看到的其他答案看起来都非常复杂,而在我看来,FSM是如何实现的; 它的美丽在于它的简洁性和FSM可能非常复杂,有许多很多状态和转换,但它们允许复杂的过程容易被分解和消化.
我意识到我的回答不应该包括另一个问题,但我不得不问:为什么这些其他提议的解决方案看起来如此复杂?
它们似乎类似于用巨大的大锤击打小钉子.
我用朱丽叶的代码制作了这个通用状态机。它对我来说非常棒。
这些是好处:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
这是 TryGetNext 方法的返回类型:
public struct TransitionResult<TState>
{
public TransitionResult(TState newState, bool isValid)
{
NewState = newState;
IsValid = isValid;
}
public TState NewState;
public bool IsValid;
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
使用派生状态机
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);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
224056 次 |
| 最近记录: |