|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Contents
IntroductionState machines have always fascinated me. There is a clockwork precision to their inner workings that appeals to me on an aesthetic level. They are also an invaluable programming tool. In building libraries and applications, I have returned to them again and again. The .NET State Machine Toolkit grew out of my interest in state machines as well as my need for a small framework to create them. This is the first of three articles about my .NET State Machine Toolkit. This article will cover the classes that make up the core of the toolkit as well as how to create a simple, flat state machine. Part II will cover creating hierarchical state machines as well as some of the more advanced features. Part III will cover code generation as well as creating state machines with XML. Special thanks to Marc Clifton for suggesting how I could break up my article into several parts. I had been struggling with this, and his suggestion made things clear. Thanks, Marc! What are state machines?A state machine is a model of how something behaves in response to events. It models behavior by making its responses appropriate to the state that it is currently in. How a state machine responds to an event is called a transition. A transition describes what happens when a state machine receives an event based on its current state. Usually, but not always, the way a state machine responds to an event is to take some sort of action and change its state. A state machine will sometimes test a condition to make sure it is true before performing a transition. This is called a guard.
This description of state machines introduces several abstract concepts quickly, and is not meant to be formal or complete. It is just a starting point. We will explore what state machines are, more through example than definition. A light switch state machineWe will look at a very simple state machine, a light switch. It has only two states: on and off. When the light switch is in the off state and receives an event turning it on, it transitions to the on state. When the light switch is in the on state and receives an event turning it off, it transitions to the off state. This is about as simple as it gets for state machines.
The above state chart diagram illustrates the light switch state machine. States are represented by rounded rectangles. Transitions are represented by arrowed curved lines connecting the states. The arrows indicate the direction of the transition, and the lines are labeled with the name of the event that triggered the transition. When a state machine is created, it begins its life in one of its states. This state is called the initial state. A solid circle connected by an arrowed line points to the initial state. In the case of our light switch state machine, the initial state is the off state. State charts can include other details as well. For example, each transition can be labeled with an action that describes what action the state machine performs during the transition. A transition can be labeled with a guard as well. For a more in depth look at state charts, go here. The .NET State Machine ToolkitWith that brief introduction to state machines, we will now look at the .NET State Machine Toolkit. It is made up of a small number of classes described below. In addition to these classes are a number of classes used for code generation, which I will cover in Part III. I will describe the role of each class as well as some important points about how they behave. The StateMachine classThe Sending an event to a A
A client that uses a The ActiveStateMachine classThe The The PassiveStateMachine classUnlike the Because the The State classThe When a After processing an event, the The SubstateCollection classThe Substates are not represented by their own class. The There are some restrictions on which The Transition classThe The TransitionCollection classThe When a Implementing the light switch state machineLet's use the toolkit to build the light switch state machine described above. It will have two states: using System;
using Sanford.StateMachineToolkit;
namespace LightSwitchDemo
{
public class LightSwitch : PassiveStateMachine
{
public LightSwitch()
{
}
#region Entry/Exit Methods
#endregion
#region Action Methods
#endregion
}
}
This is the skeleton for our Events are represented in the toolkit as integers. The values of the integers serve as IDs for the events. It is easiest to represent event IDs with an enumeration. States are represented by the using System;
using Sanford.StateMachineToolkit;
namespace LightSwitchDemo
{
public class LightSwitch : StateMachine
{
public enum EventID
{
TurnOn,
TurnOff
}
public enum StateID
{
On,
Off
}
private State on;
private State off;
// ...
We made the enumerations public so that clients listening to the State machine methodsBefore going any further, let's add all of the methods for our state machine: using System;
using Sanford.StateMachineToolkit;
namespace LightSwitchDemo
{
public class LightSwitch : StateMachine
{
private enum EventID
{
TurnOn,
TurnOff
}
public enum StateID
{
On,
Off
}
private State on;
private State off;
private State disposed;
public LightSwitch()
{
}
#region Entry/Exit Methods
private void EnterOn()
{
Console.WriteLine("Entering On state.");
}
private void ExitOn()
{
Console.WriteLine("Exiting On state.");
}
private void EnterOff()
{
Console.WriteLine("Entering Off state.");
}
private void ExitOff()
{
Console.WriteLine("Exiting Off state.");
}
#endregion
#region Action Methods
private void TurnOn(object[] args)
{
Console.WriteLine("Light switch turned on.");
ActionResult = "Turned on the light switch.";
}
private void TurnOff(object[] args)
{
Console.WriteLine("Light switch turned off.");
ActionResult = "Turned off the light switch.";
}
#endregion
}
}
In previous versions of the toolkit, I described using "Facade" methods to serve as light wrappers for sending events to the The Entry and Exit methods are optional. Entry methods are called by Next, we have the Action methods. These methods represent the actions that are performed during transitions. Notice that they take an object array as their only parameter. This array represents the arguments passed to the In addition to the methods described above, you can have Guard methods. These are methods Before we leave the In the case of
And in the case of
At this point, the steps for both the passive and active state machines are the same:
Passive state machines will continue dispatching events until their event queue is empty. Creating states and transitionsNext, let's create the public LightSwitch()
{
off = new State((int)StateID.Off,
new EntryHandler(EnterOff),
new ExitHandler(ExitOff));
on = new State((int)StateID.On,
new EntryHandler(EnterOn),
new ExitHandler(ExitOn));
}
Each In previous versions of the toolkit, Now, let's set up the state transitions so that when the state machine is in the public LightSwitch()
{
off = new State((int)StateID.Off, 3,
new EntryHandler(EnterOff),
new ExitHandler(ExitOff));
on = new State((int)StateID.On, 3,
new EntryHandler(EnterOn),
new ExitHandler(ExitOn));
Transition trans;
trans = new Transition(on);
trans.Actions.Add(new ActionHandler(TurnOn));
off.Transitions.Add((int)EventID.TurnOn, trans);
trans = new Transition(off);
trans.Actions.Add(new ActionHandler(TurnOff));
on.Transitions.Add((int)EventID.TurnOff, trans);
Initialize(off);
}
When we created a Before leaving the constructor, we initialized the LightSwitch demoWe are now ready to write a simple driver program to demonstrate our using System;
using System.Threading;
using Sanford.StateMachineToolkit;
namespace LightSwitchDemo
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
LightSwitch ls = new LightSwitch();
ls.TransitionCompleted +=
new TransitionCompletedEventHandler(HandleTransitionCompleted);
ls.Send((int)LightSwitch.EventID.TurnOn);
ls.Send((int)LightSwitch.EventID.TurnOff);
ls.Send((int)LightSwitch.EventID.TurnOn);
ls.Send((int)LightSwitch.EventID.TurnOff);
ls.Execute();
Console.Read();
}
private static void HandleTransitionCompleted(object sender,
TransitionCompletedEventArgs e)
{
Console.WriteLine("Transition Completed:");
Console.WriteLine("\tState ID: {0}",
((LightSwitch.StateID)(e.StateID)).ToString());
Console.WriteLine("\tEvent ID: {0}",
((LightSwitch.EventID)(e.EventID)).ToString());
if(e.Error != null)
{
Console.WriteLine("\tException: {0}", e.Error.Message);
}
else
{
Console.WriteLine("\tException: No exception was thrown.");
}
if(e.ActionResult != null)
{
Console.WriteLine("\tAction Result: {0}",
e.ActionResult.ToString());
}
else
{
Console.WriteLine("\tAction Result: No action result.");
}
}
}
}
With the results when run: Entering Off state.
Entering Off state.
Exiting Off state.
Light switch turned on.
Entering On state.
Transition Completed:
State ID: On
Event ID: TurnOn
Exception: No exception was thrown.
Action Result: Turned on the light switch.
Exiting On state.
Light switch turned off.
Entering Off state.
Transition Completed:
State ID: Off
Event ID: TurnOff
Exception: No exception was thrown.
Action Result: Turned off the light switch.
Exiting Off state.
Light switch turned on.
Entering On state.
Transition Completed:
State ID: On
Event ID: TurnOn
Exception: No exception was thrown.
Action Result: Turned on the light switch.
Exiting On state.
Light switch turned off.
Entering Off state.
Transition Completed:
State ID: Off
Event ID: TurnOff
Exception: No exception was thrown.
Action Result: Turned off the light switch.
Notice that the DependenciesWhen you download the demo projects, which includes the source code for the toolkit, you'll find that it won't build out of the box. This is because I've deleted the bin and obj folders from each project to make the zip file smaller. These folders contain the assemblies the projects depend on to build. For this reason, you'll need to go to my website to download the assemblies the State Machine Toolkit depends on. Then you'll need to add them to each project in the solution manually. The State Machine Toolkit depends on two other of my namespaces, the The dependency on the ConclusionAs I said at the beginning, I find state machines appealing and fascinating. This toolkit has been incredibly satisfying to write. It has continued to evolve, and I hope that this latest version will be the easiest version to use yet. If you have found this article interesting and the toolkit looks useful to you, please take a look at Part II and Part III. Take care, and as always, comments and suggestions are welcome. History
| ||||||||||||||||||||