Building a better .NET State Machine

[Note: Updated version on Nuget has slightly different API, see latest blog post.]

There are several state machine implementations for .NET out there but, sadly, none of them met all of the requirements I have for a state machine. These are:-

​1) Well written using encapsulation and other good practices 2) Able to be easily serialized to disk 3) Able to handle temporal events easily (After … At … Every …) 4) Disk serialized form must expose a property saying when it next needs to be fetched from disk to run 5) Implements hierarchical states with entry and exit actions

So I built one, and have made the source code available on Nuget so you can add it to any project easily without any extra DLLs.

Look for “AboditStateMachine“ on Nuget to download it. The download includes a sample state machine documented to show off some of its capabilities.

Defining states is easy, just give them a name and specify their parent state if any:-

[csharp] public static readonly State UnVerified = AddState(“UnVerified”);

public static readonly State Verified = AddState(“Verified”);

// States are hierarchical. If you are in state VerifiedRecently you are also in is parent state Verified.

public static readonly State VerifiedRecently = AddState(“Verified recently”, parent: Verified); public static readonly State VerifiedAWhileAgo = AddState(“Verified a while ago”, parent: Verified);

[/csharp]

You can use any other type that’s IEquatable as an Event type or you can use the provided Event class:

[csharp] private static Event eUserVerifiedEmail = new Event(“User verified email”); private static Event eScheduledCheck = new Event(“Scheduled Check”); private static Event eBeenHereAWhile = new Event(“Been here a while”); [/csharp]

The state machine itself is specified in a static constructor so it runs just once no matter how many instances of the state machine you create. Each method is provided with an instance of the state machine ‘m’ as well as the state ‘s’ and the event ‘e’ as appropriate:

[csharp] static DemoStatemachine() { UnVerified .OnEnter((m, s, e) => { // States can execute code when they are entered or when they are left // In this case we start a timer to bug the user until they confirm their email m.Every(new TimeSpan(hours: 10, minutes:0, seconds:0), eScheduledCheck);

// You can also set a reminder to happen at a specific time, or after a given interval just once m.At(new DateTime(DateTime.Now.Year+1, 1, 1), eScheduledCheck); m.After(new TimeSpan(hours: 24, minutes: 0, seconds: 0), eScheduledCheck);

// All necessary timing information is serialized with the state machine // The serialized state machine also exposes a property showing when it next needs to be woken up // External code will need to call the Tick(utc) method at that time to trigger the next temporal event

}

) .When(eScheduledCheck, (m, s, e) => { Trace.WriteLine(“Here is where we would send a message to the user asking them to verify their email”); // We return the current state ‘s’ rather than ‘UnVerified’ in case we are in a child state of ‘Unverified’ // This makes it easy to handle hierarchical states and to either change to a different state or stay in the same state return s; }) .When(eUserVerifiedEmail, (m, s, e) => { Trace.WriteLine(“The user has verified their email address, we are done (almost)”); // Kill the scheduled check event, we no longer need it m.CancelScheduledEvent(eScheduledCheck); // Start a timer for one last transition m.After(new TimeSpan(hours:24, minutes:0, seconds:0), eBeenHereAWhile); return VerifiedRecently; });

VerifiedRecently .When(eBeenHereAWhile, (m, s, e) => { Trace.WriteLine(“User has now been a member for over 24 hours - give them additional priviledges for example”); // No need to cancel the eBeenHereAWhile event because it wasn’t auto-repeating //m.CancelScheduledEvent(eBeenHereAWhile); return VerifiedAWhileAgo; });

Verified.OnEnter((m, s, e) => { Trace.WriteLine(“The user is now fully verified”); });

VerifiedAWhileAgo.OnEnter((m, s, e) => { Trace.WriteLine(“The user has been verified for over 24 hours”); });

} [/csharp]

With your state machine defined you can now create instances of it, trigger events on them, serialize them to disk, fetch them back, carry on eventing on them, …

[csharp] DemoStatemachine demoStateMachine = new DemoStatemachine(DemoStatemachine.UnVerified);

// At the time specified in demoStateMachine.NextTimedEventAt you reload the state machine from disk and call demoStateMachine.Tick(DateTime.UtcNow);

// When the user verifies their email address you call … demoStateMachine.VerifiesEmail();

// At any other time you can examine the current state, act on the state changed event, …

[/csharp]

I hope you find this new state machine implementation useful, and if you have any feedback, do please send it my way.



Sun Apr 15 2012 07:38:27 GMT-0700 (Pacific Daylight Time)


Next page: Updated Release of the Abodit State Machine

Previous page: Sequential Logic Blocks - compared to the Reactive Framework


Disqus goes here