Click here to Skip to main content
Click here to Skip to main content
Go to top

Finite State Machine and Multithreading using .NET

, 2 Mar 2006
Rate this:
Please Sign up or sign in to vote.
An article on classes for finite state machines, events and threads.

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

  • Supports multithreading
  • Supports multiple FSMs on one thread
  • Supports timer events
  • Allows definition of transition handlers and / or event handlers
  • Supports state entry and exit handlers
  • Allows definition default transition and event handlers
  • Minimal effort to write an FSM, you write simply less code
  • Flexible through optional usage of attributes
  • Allows one-to-one translation from UML state diagrams

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:

  • Try to dispatch the event to a transition handler.
  • If not existing, try to dispatch the event to a default transition handler.
  • If not existing, try to dispatch the event to a event handler.
  • If not existing, call the default event handler.
  • If not overridden, output an error trace.

Without states:

  • If not existing, try to dispatch the event to a event handler.
  • If not existing, call the default event handler.
  • If not overridden, output an error trace.

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

  • December 1, 2003 - Initial version.
  • December 23, 2003 - Added default transition handler and completed description.
  • April 27, 2005 - Fixed some bugs and improved documentation.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Linus Flüeler
Web Developer Stadt Winterthur
Switzerland Switzerland
No Biography provided

Comments and Discussions

 
GeneralMaintaining the state variable Pinmember.andy.b.18-Sep-09 2:56 
Generalsteed.net Pinmembermarc.thom8-Jun-06 11:24 
GeneralSync Events PinmemberTash Robinson6-Oct-05 7:05 
GeneralRe: Sync Events PinmemberLinus Flüeler6-Oct-05 20:59 
GeneralRe: Sync Events PinmemberTash Robinson7-Oct-05 3:18 
GeneralRe: Sync Events PinmemberLinus Flüeler24-Oct-05 1:26 
GeneralRe: Sync Events PinmemberLinus Flüeler24-Oct-05 1:38 
GeneralNo need of BeginInvoke internals in DiningPhilosophers class PinmemberIlan120-Feb-04 21:31 
GeneralRe: No need of BeginInvoke internals in DiningPhilosophers class PinmemberLinus Flüeler3-Mar-04 4:49 
QuestionHow to handle default events PER state PinmemberMtnBiknGuy12-Dec-03 16:36 
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
AnswerRe: How to handle default events PER state PinmemberLinus Flüeler14-Dec-03 20:04 
GeneralRe: How to handle default events PER state PinmemberMtnBiknGuy15-Dec-03 7:06 
QuestionWhat does the comment mean? PinmemberMtnBiknGuy9-Dec-03 10:34 
AnswerRe: What does the comment mean? PinmemberLinus Flüeler9-Dec-03 20:51 
GeneralRe: What does the comment mean? PinmemberMtnBiknGuy10-Dec-03 6:41 
GeneralFSM are just the beginning Pinmemberafisser8-Dec-03 21:49 
GeneralRe: FSM are just the beginning PinmemberJoseph Healy21-Jan-04 0:46 
GeneralRe: FSM are just the beginning PinmemberHypnotron18-Mar-06 21:13 
GeneralQuestion on transitions PinmemberKatNel4-Dec-03 9:25 
GeneralRe: Question on transitions PinmemberLinus Flüeler5-Dec-03 4:39 
GeneralRe: Question on transitions Pinsussdeanige28-Jan-04 13:01 
GeneralRe: Question on transitions PinmemberLinus Flüeler29-Jan-04 0:51 
QuestionApplications for this? PinmemberdzCepheus3-Dec-03 18:53 
AnswerRe: Applications for this? PinmemberTom Fischer4-Dec-03 9:19 
GeneralRe: Applications for this? PinmemberdzCepheus4-Dec-03 9:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 2 Mar 2006
Article Copyright 2003 by Linus Flüeler
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid