Click here to Skip to main content
Email Password   helpLost your password?

Sample Image

Motivation

In automation and other multi-threading programming disciplines, the Finite State Machine (FSM) is an important model for designing dynamic behavior. The FSM is an object, which is always in one state of a defined set of states. It is able to receive events from a set of events, depending on the actual state. Upon receiving a given event, the FSM executes a specific transition. I created a framework of classes which simplifies the implementation of FSMs in .NET applications.

Background

A common approach to implement an FSM is a cross-table with the states on one axis and the events on the other axis. In the crossfields, you define the actions (if there are any). Unfortunately, this approach is not object-oriented. Another approach comes from �the Gang of Four� (Gamma, etc.) with their design pattern �States�. This is an object-oriented approach with a class for each event and each state. Even if this is a very flexible solution, it is somewhat �talkative�: you need a lot of classes and relations between these classes for a single FSM.

The approach described here is also object oriented, but requires less code than the "States"-pattern. You write handler methods for your Fsm class. But you need not bind them to events and states. This is done automatically in the startup of your program. In the startup phase, the Fsm gathers all necessary information from your source by the reflection capabilities of .NET. You just have to follow some naming and signature rules, that's it! Or, if you want to have more control over the binding, you can tag classes, members and methods with some attributes. The price you have to pay is some �magic� behavior behind the code and some loss of transparency.

Features

Implementing an FSM, Step by Step

  1. Derive a class from the RTLib base class Fsm. Add a state enumeration type and a state member state. Override the GetCurrentState method. Initialize state with the initial state:
    public class MyFsm : RTLib.Fsm
    {
        public enum MyState 
        {
            MyState1, 
            MyState2, 
            MyState3
        };
    
        public MyFsm ()
        {
            state = MyState.MyState1;
        }
    
        private MyState state;
    
        override protected int GetCurrentState()
        {
            return (int)state;
        }
    }

    Note: The only reason for GetCurrentState() is that the Fsm class gets to know the current state. I did not find a way to create it automatically by reflection.

  2. Define your event classes. Every event type is a class derived from the base class FsmEvent. There exists special event base classes for timer events and synchronizable events. Even it is possible to define them public, they are usually private to your Fsm as nested classes. Examples:
    private class MyEvent : RTLib.FsmEvent
    {
        public MyEvent()
        {
        }
    }
    
    private class MyEventWithParam : RTLib.FsmEvent
    {
        public MyEventWithParam(Param1Type param1)
        {
            this.param1 = param1
        }
    
        public Param1Type Param1
        {
            get { return param1;}
        }
    
        private Param1Type param1;
    }
    
    private class MyTimerEvent : FsmTimerEvent
    {
        public MyEventA()
            : base( new TimeSpan(0,0,6) ) // 6 sec
    
        {
        }
    }
    
    private class MyAbsoluteTimerEvent : FsmTimerEvent
    {
        public MyEventA(): base( new DateTime(DateTime.Now.Year, 
                DateTime.Now.Month, DateTime.Now.Day,
                12, 0, 0) ) // Today at noon (12:00:00)
    
        {
        }
    }
  3. Define your transition-, event- and state handlers.
    Transition Handler A method, which is called upon receiving a specific event in a certain state. Either you can use the naming convention State_Xxxxx(MyEvent) or you can define a method attribute [FsmTransitionHandler("State")]. If you define a transition handler with an event parameter of the base event class FsmEvent, it is a default transition handler for this state. This can be used to implement default handling for unexpected events.
    Event Handler A method, which is called upon receiving a specific event in an FSM without states or with no transition handler for this event and the current state. Either you can use the naming convention Xxxxx(MyEvent) or you can define a method attribute [FsmEventHandler].
    State Handler A method, which is called upon entering or on exit of a certain state. Either you can use the naming convention State_EntryState(), respective State_ExitState() or you can define a method attribute [FsmStateHandler("State", EStateHandlerType.Entry )], respective [FsmStateHandler("State", EStateHandlerType.Exit )].

    Important note: Attributes can be used only if your FSM class has the following attribute: [FsmCoding(ECodingType.WithAttributes)]. If you want to suppress resolution of the naming convention in an FSM without attribute usage, you can use the method attribute [FsmNoHandler] for any kind of handler methods.

    Examples:

    // A transition handler
    
    private void MyState1_OnMyEventWithParam( MyEventWithParam ev )
    {
        Trace.Write(DateTime.Now+" "+GetType().Name+": ");
        Trace.Write("MyState1_OnMyEventWithParam, Param = ");
        Trace.WriteLine(ev.Param1);
        state = MyState.MyState2;
    }
    
    // A transition handler with attribute
    
    [FsmTransitionHandler("MyState1")]
    private void TransitionHandlerWithAttribute( MyEvent ev )
    {
        Trace.Write(DateTime.Now+" "+GetType().Name+": ");
        Trace.Write("TransitionhandlerWithAttribute");
        state = MyState.MyState2;
    }
    
    // A state handler
    
    private void MyState1_EntryState( FsmEvent ev,  State oldState)
    {
        Trace.Write(DateTime.Now+" "+GetType().Name+": ");
        Trace.WriteLine("MyState1_EntryState, oldState = "+oldState.ToString() );
    }
    
    // An event handler
    
    private void OnMyEventWithParam( MyEvent ev )
    {
        Trace.Write(DateTime.Now+" "+GetType().Name+": ");
        Trace.WriteLine("OnMyEvent");
    }
  4. Create an FsmProcessor object and push the FSM to it. If you want to synchronize to completion of the FSM, add also a Wait() call:
    FsmProcessor processor = new FsmProcessor("MyProc");
    MyFsm myFsm = new MyFsm();
    processor.PushFsm(myFsm);
    myFsm.Wait();

    Note: With Wait(), it is also possible to synchronize to the processing of a posted event if it is derived from SyncEvent. Fsm and EventBase have a constructor with a synchronized parameter.

  5. There are several ways to terminate the FSM:
    FSM Internally In any handler, call the base method TerminateFsm()
    FSM Externally and FSM Internally Call Terminate() on Fsm or TerminateFsm(Fsm) on FsmProcessor
    FSM Externally Call TerminateAllFsm(delay) on FsmProcessor

Classes in RTLib

Sample Image

Fsm Base class for all FSMs. Implements all the "magics" with reflection. Has some overridables for entry and exit-code. Synchronization to completion can be enabled.
FsmProcessor Encapsulates a thread for the usage with FSMs. It maintains an event queue, which is processed in the context of the thread. FSMs and events can be posted to this queue.
FsmEvent Base class for all events used with FSMs. To send an event to a FSM it should be posted to the processor the FSM is running on. There exists a constructor which allows to create a synchronized event like with FsmSyncEvent.
FsmSyncEvent Base class for events you want to synchronize to its completion. Actually, FsmEvent supports a sync event which is enabled in the constructor of FsmSyncEvent.
FsmTimerEvent A specialized FsmEvent for timer usage. It can be used for single shot, n repeats or endless repeats. You can define relative timers (with TimeSpan parameter) or absolute timers (with DateTime parameter). Fsm is the base class of all FSMs. Is is also derived from FsmEventBase.

FSM Entry and Exit Code

When an FSM is posted, the thread associated with the FSM processor calls first the override method OnFsmEntry(). When an FSM terminates the thread associated with the FSM processor calls the override method OnFsmExit() just before the FSM object is deleted. By overriding these methods, the user has a chance to execute some initialization and clean-up code in the context of the FSM processor thread the FSM is executing on.

Event Processing

For performance reasons, all time consuming reflection code is done at start-up time. At runtime, just a few look-ups are necessary to find and call the right handler method. An FSM can have a state variable or not. This has influence to the event handling. With states, the FSM handles the events in the following order:

With states:

Without states:

Attributes and Naming Convention

Attribute Naming Convention Description
FsmCoding Default Used for an Fsm class to force using attributes (ECodingType.WithAttributes) or naming convention and signature match (ECodingType.Automatic) is used. Default is ECodingType.Automatic. There is one attribute used with automatic: FsmNoHandler
FsmNoHandler [FsmNoHandler] Discriminator in ECodingType.Automatic mode. Allows to exclude a method from FSM processing even if it follows the naming convention.
FsmState state Defines the state variable in the FSM. The variable must be of type enum. With FsmCoding ECodingType.Automatic, the state variable must have the name state.
FsmTransitionHandler MyState_Xxxxx(MyEventClass event)
MyState_Xxxxx(FsmEvent event)
Defines a transition handler method for a specific state and a specific event. The state must be passed as a string parameter to the attribute. With FsmCoding ECodingType.Automatic, the transition handler method must follow the naming convention MyState_Xxxxx(MyEventClass event). It is possible to define a default transition handler: with MyState_Xxxxx(FsmEvent event), the FSM catches all events which are not handled by the other transition handlers for this state.
FsmStateHandler MyState_EntryState()
MyState_ExitState()
Defines a state handler method. A state handler method is non-static and must have two and only two parameters: FsmEvent event and MyState oldState. The state must be passed as a string parameter to the attribute. The type of state handler must be passed as the second parameter: EStateHandlerType.Entry = state entry handler, EStateHandlerType.Exit = state exit handler. With FsmCoding ECodingType.Automatic, the state handler must follow the naming convention MyState_EntryState(), respective MyState_ExitState().
FsmEventHandler Xxxxx(MyEventClass event) Defines an event handler method for events defined by the event parameter in the signature of the method. A event handler method is non-static and must have one and only one parameter, derived from FsmEvent. With FsmCoding ECodingType.Automatic, the event handler must follow the naming convention Xxxxx(MyEventClass event).

Sample 1: RTLibConsoleTest

This program is a console application and does nothing useful. There are two FSMs:

MyFsm1 Without attributes, using naming convention only
MyFsm2 Using attributes only

Sample 2: Dining Philosophers

Sample Image

Five philosophers sit around a circular table. Each philosopher spends his life alternatively thinking and eating. In the center of the table is a large plate of spaghetti. A philosopher needs two forks to eat a helping of spaghetti. Unfortunately, as philosophy is not as well paid as computing, the philosophers can only afford five forks. One fork is placed between each pair of philosophers and they agree that each will only use the fork to his immediate right and left. Philosophers are depicted in yellow when they are thinking, blue when hungry and green when eating.

Points of Interest

While programming FSM, I learned something about reflection and threading.

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralMaintaining the state variable
.andy.b.
3:56 18 Sep '09  
Use the following code snippets to get the base class to maintain the state variable. My motivation for this is: (a) I'm lazy; (b) I can never remember the fine detail to make things like this work.

As the state variable is now object, no casting is required. Runtime checking can use if (value.Type() != _stateType) to check that correct types are passed to the runtime.

// Add to the base class fields
Type _stateType; // this is the type of the state enumeration
object _stateVar; // The state variable

// add to FSM startup methods - _stateType is the type of the state enumeration found by reflection
// Create the state variable of type _stateType
_stateVar = _stateType.InvokeMember(_stateType.Name, BindingFlags.CreateInstance, null, null, null);

    ...
// replacement base class property to access the current state of the FSM
protected object GetCurrentState()
{
return _stateVar;
}
...

Andy
Generalsteed.net
marc.thom
12:24 8 Jun '06  
For your interest in using statemachines in real systems look at www.steed.de. You can build UI based Aplication as Services or C/S applications with it. I decreases developmentime an programming efford. And you can easy monitor your running applicaton.


GeneralSync Events
Tash Robinson
8:05 6 Oct '05  
I have used your framework for an automation project I am working on. It is working very well, and reduced my development time considerably. Many thanks for that.

The only problem I have run into is that some events I have are time critical (+/-20 milliseconds accuracy is needed), the FSM timer events are sometimes out of this range.

I noticed that you have SyncEvents, but I have not tried to use them. Can you explain more about what they are for and how they are used?

Is there a way to set a "priority" for certain events?

Thanks again and best regards.
GeneralRe: Sync Events
Linus Flüeler
21:59 6 Oct '05  
Hi Tash

Thank you for your feedback.

About the timer issue I will look into that problem sasp.

About SyncEvents: In the article on chapter "Implementing an FSM, step by step", Point 4, you will find the Note: "With Wait(), it is also possible to synchronize to the processing of a posted event if it is derived from SyncEvent. Fsm and EventBase have a constructor with a synchronized parameter."
It is basically to synchronize to the treatment of the event. It is also possible to synchronize to the completion of an FSM.

I like your idea to introduce a "priority" to events. As soon as I have some time left, I will implement that. Do you think, "high" and "low" is sufficient or do we need more values?

Best regards Linus




GeneralRe: Sync Events
Tash Robinson
4:18 7 Oct '05  
Linus,

Thanks for your reply. I had missed point 4 you mentioned above, thanks for pointing that out. Would this be the correct way to sync to the completion of an event?

private class mySyncEvent : FsmSyncEvent

then in a code section

mySyncEvent ev = new mySyncEvent();
PushEvent(ev);
Wait();

Would this then wait until the event was processed? When does it continue? Is it at the completetion of the event handler? If the event caused a state transition, does the transition happen first?

As for the timer events, in my initial testing they seemed to always fire in a very timely manner. But the project has become more complex. There is a form which controls 2 FSMs. I added a timer control to the form object and it has an event which is firing every 100ms. This is not really a time critical process, just a time display for the user, and some logic to trigger some FSM events if certain conditions apply. Once this timer was added I noticed the FSM timer events were then taking slightly longer, I assume it is because the processor can not service them quickly enough with so many other things happening. That is why I was wondering about priorities, if there is a way to say certain events need to be a "high" priority that would be excellent. For my purposes just "high" and "low" would be fine.

Many thanks for you help and advice.

Best regards,
Tash Robinson
GeneralRe: Sync Events
Linus Flüeler
2:26 24 Oct '05  
> Would this then wait until the event was processed?
Yes
> When does it continue? Is it at the completetion of the event handler?
Yes
> If the event caused a state transition, does the transition happen first?
Yes

GeneralRe: Sync Events
Linus Flüeler
2:38 24 Oct '05  
Concerning the timer problem: It is important not to "block" an event or transition handler. If such a handler is executing a time consuming operation, the event queue is not checked in the meantime. That means also, that timer event may elapse undetected.
If an event handler has to execute a time consuming operation, it should
- delegate it to another FSM on another processor (=thread), or
- use an asynchronous variant of the op which sends back an event on completion
GeneralNo need of BeginInvoke internals in DiningPhilosophers class
Ilan1
22:31 20 Feb '04  
If you change the delegate signature of OnChangeForkState(int ForkHashCode,Fork.State state) to OnChangeForkState(Fork fork,Fork.State state) you will be able to directly register it with the Fork event ChangeState:
fork.ChangeState += new Fork.OnChangeStateDelegate(OnChangeForkState); Add the line of computing hash code internally. This way you get rid of declaring m_ChangeForkStateDelegate, the delegate itself and the ChangeForkState intermediate which uses the the BeginInvoke internal function.
The same applies to the Philosopher case.
I liked your article and learned a lot.
Thank You

Ilan
GeneralRe: No need of BeginInvoke internals in DiningPhilosophers class
Linus Flüeler
5:49 3 Mar '04  
Thank you for your finding. You are right, your solution is more elegant. I will change that in my test program for the next version.
And I am honored that you enjoyed my aricle.

Linus
GeneralHow to handle default events PER state
MtnBiknGuy
17:36 12 Dec '03  
Hi Linus,
In your examples you show handling a specific event in an FSM without states or with no transition handler for that event and the current state. However, I would appreciate an example showing how to handle such a situation within the context of specific states.

For example, let's say I have StateA and I have defined 3 events that are handled by transition handlers for StateA. However, if any other event is received while in StateA, I want to transition to StateB. This would only happen if the event is not one of the 3 already handled (i.e., a default case).

This handling needs to be specific for StateA. For example, if I have an analogous situation (a default case) in StateC, I might need to transition to StateF (not StateB as I did earlier).

The obvious way to do this is to use a switch statement inside the FSM's EventHandler but this doesn't seem very satisfactory. Can you give me some suggestions and an example?

Thanks!
Mountain
GeneralRe: How to handle default events PER state
Linus Flüeler
21:04 14 Dec '03  
Hi Mountain

You are right. My solution does not support a default transition handler for a specific state.
The actual situation (Fsm::OnEvent):
If FSM does not find a transition, it tries to find an event handler for this event.
If it does not find an event handler, it calls the virtual method "OnFsmEventDefault"

I will enhance FSM this way, that the user will be able to define a default transition handler for every state. This transition handler will handle all events received in StateA where no transition handler is defined for.
For automatic mode, the method name will be "StateA_DefaultTransition".
The attribute will be [FsmDefaultTransitionHandler("StateA")]

Satisfied?

If you are patient, give me a few days to implement it.
Otherwise, do it yourself.

Linus



GeneralRe: How to handle default events PER state
MtnBiknGuy
8:06 15 Dec '03  
Hi Linus,
I think that will be an excellent enhancement for FSM. I will wait on you to implement it. However, when you get it done, I'll try to make the time to post an example showing FSM using this new feature. I think it could be a nice example for you. It will be a calculator implemented on FSM.
Best regards,
Mountain

GeneralWhat does the comment mean?
MtnBiknGuy
11:34 9 Dec '03  
// Viellecht könnte man diesen override dynamisch in Fsm
// erzeugen!!!

This is in the code. It looks important. What is the english translation?
GeneralRe: What does the comment mean?
Linus Flüeler
21:51 9 Dec '03  
It is german and means: Probably it is possible to create this override dynamically.
I felt that it is not so nice, that the user of Fsm has always write this override "GetCurrentState". The reason is, that the Fsm class has to know the current state. But the state enumeration is user-specific. Perhaps it is possible to generate this method dynamically by reflection.
This issue is not so important, in spite of the 3 exclamation marks.

GeneralRe: What does the comment mean?
MtnBiknGuy
7:41 10 Dec '03  
Thanks. The 3 exclamation marks made it look important to someone who doesn't read German.
GeneralFSM are just the beginning
afisser
22:49 8 Dec '03  
Very nice Article; I must admit that I have not tried the code, I'm not in the .NET business Wink But everybody really interested in state machines should read the book "Practical Statecharts in C/C++" from Miro Samek. It goes much more in detail (of course, it's a whole Book about the topic) and provides a framework for hierachical state machine as defined in the UML standard. And the author claims his code is production ready.

Andre


GeneralRe: FSM are just the beginning
Joseph Healy
1:46 21 Jan '04  
qf4net: Quantum Framework for .Net
Hierarchical State Machine and Active Objects

"Here you can always find the latest version of the C# port of Miro Samek's excellent implementation of a hierarchical state machine and his Quantum Framework for active objects."

http://www.hessmer.org/dev/qhsm/

GeneralRe: FSM are just the beginning
Hypnotron
22:13 18 Mar '06  
That does look very nice indeed. I've scrapped my FSM implementation for Dr Hessmer's QF port however i'm curious about the BSD style license it employs. As its a direct port of Quantum Leap's framework which uses a dual license GPL + Commercial scheme, I'm wondering if his license is valid?

http://www.quantum-leaps.com/licensing/com.htm[^]

In any case, Quantum Leap's commercial licensing prices are very reasonable... well worth the time saving of developing a flexible, thread safe, and debugged FSM framework from scratch.

I'm going to put it through the paces in some test apps of my own and if everything checks out, I'm just going to focus my time on a gui designer\code generator for it.

-- modified at 5:00 Sunday 19th March, 2006

FYI, I got an email back from Dr Hessmer and I'm sure he wouldnt mind me relaying part of his reply. Here is the relevant answers by him, to the above:

"I explicitly checked the licensing situation with Miro Samek when I did the port. Your assumption is right: The C# port is solely covered by the BSD-esque license listed on my Web site.

Miro only started using the dual GPL and commercial license in later versions that he offers on his web site."

--
So there you have it. Its free to use for any purpose.
GeneralQuestion on transitions
KatNel
10:25 4 Dec '03  
When I run the ConsoleTest project, the order I see is:
StateX_EntryState
StateX_ExitState
StateX_OnMyEventX

Why is the Exit before the transition?


Kathy.
GeneralRe: Question on transitions
Linus Flüeler
5:39 5 Dec '03  
This happends since the transition StateX_OnMyEventX is executed AFTER leaving StateX.
The order is like this:
1. Enter state StateX (upon starting the FSM or as a reaction to an event A)--> StateX_EntryState
2. Another event B is received
3. Since there exists a transition handler for event B, the FSM leaves StateX --> call StateX_ExitState
4. Now the transition can be executed --> StateX_OnB, here, the new state StateY is defined
5. Enter state StateY

I unterstand that it is samewhat confusing, but on my opinion, the FSM has to leave the state before it can execute the transition (which usually defines a new state to enter). But I am curious about discussion about this subject since I am not sure in this point.
The code to it you will find in fsm.cs, line 174
GeneralRe: Question on transitions
deanige
14:01 28 Jan '04  
Are you saying that the machine is "Stateless" while the transition occurs ?

It leaves a no-mans land. The potential for encountering it depends entirely on the length of transition. Should we therefore include transition states in our list ?

For example:
Instead of two states: Idle, Running
Use: Idle, Commencing, Running, Finishing
where (threaded ?) processing is done during Commencing & Finishing

To my mind, machines ought not be able to exist "stateless".

If this is a silly question, please forgive my ignorance.Unsure
GeneralRe: Question on transitions
Linus Flüeler
1:51 29 Jan '04  
No, the FSM is never stateless, except if you have not defined a state variable.
The idea of a transition is, that it does not consume time. It is even a mistake to call a blocking operation within a transition handler, since the thread is not able to process any FSMs and events in the meantime. Time consuming operations have to be executed asynchronously.
Your solution with additional states is a good approach. Another way is to define a second (sub-)FSM, controlling the process of your "during" transition.
Or you send a timer event to your FSM, signalling the end of the operation.

There are no silly questions, just silly answers. I hope you are sadisfied by my answer.Smile
GeneralApplications for this?
dzCepheus
19:53 3 Dec '03  
What are the applications for such a system? I've never heard of Finite State Machines, so I'm not sure why I would want to use one. Can you give some examples of real-world uses for this?

Thanks,
Ceph

Skydive -- Testing gravity, one jump at a time.
GeneralRe: Applications for this?
Tom Fischer
10:19 4 Dec '03  
Aside from the more well known uses of state machines, I've read about it being used to manage purchases on a retail website. (I always thought that was pretty cool.) Something similiar was demonstrated in an ASPToday article "Session and State Management in Distributed Web Applications".
GeneralRe: Applications for this?
dzCepheus
10:27 4 Dec '03  
Tom Fischer wrote: Aside from the more well known uses of state machines
Hehe, well, the problem is, I don't know what the well-known uses are. :p

Skydive -- Testing gravity, one jump at a time.


Last Updated 2 Mar 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010