SoFunction
Updated on 2025-03-07

A brief analysis of Stateless C# state machine

Recently, I have been trying out some control-related software designs. I think of the state machine, which is quite useful for solving the state switching of some control systems.

There are many introductions to the state machine (finite state automaton) on the Internet. A simple understanding is to define a series of states. Through a series of events, states can be switched between each other.

If you do not use the idea of ​​a state machine to program, then the programming method for the process will make the program scalability worse and it will not be easy to debug. The state machine only needs to define the events between various states and state switching. You just trigger the events, and the rest will be automatically completed by itself (after all, the name is called finite state automaton), which is a perfect adaptation for many systems that need to define various control stages. I learned that .NET also has many libraries that can implement these functions. This article mainly introduces the application of Stateless.

Stateless introduction

Stateless can create minimalist state machines with corresponding workflows. It is used by many projects (including VisualStudio Extension and AIlab).

It supports the following features:

  • Supports various types as states and trigger events
  • Support state inheritance
  • Support status entry/leave events
  • Support conditional state transition
  • Support status/transfer query

There are also a few things to note:

  • It supports asynchronous syntax, but it is single threaded and not thread-safe.
  • Can export DOT graph

It's very simple to install, just install it in nuget:

Install-Package Stateless

Stateless use

It is quite simple to use. Taking the call as an example, it is a state machine for various actions and states of making a call.
You need to define some status and events/triggers first. The phone has events such as dialing, connecting, and leaving messages, and events such as ringing, hanging, and hanging:

//The code comes from the official example, and can be found on the official github library, with slight modifications to fully display the function.enum Trigger
{
  CallDialed,
  CallConnected,
  LeftMessage,
  PlacedOnHold,
  TakenOffHold,
  PhoneHurledAgainstWall,
  MuteMicrophone,
  UnmuteMicrophone,
  SetVolume
}

enum State
{
  OffHook,
  Ringing,
  Connected,
  OnHold,
  PhoneDestroyed
}

Then create a state machine:

_machine = new StateMachine<State, Trigger>(() => _state, s => _state = s);

The last and most detailed explanation is the behavior of configuring the state machine:

/*
 In order to explain as many functions as possible, the following program has modified the official code and you can find codes that can be directly executed in the official.
 */

// Use Permit to indicate that after an event occurs, it will be transformed from one state to another._machine.Configure()
  .Permit(, );

//Set an event with parameters, this event is the type of CallDialedvar _setCalleeTrigger = _machine.SetTriggerParameters&lt;string&gt;();
_machine.Configure()
  //Allow to re-enter the current state, this process will trigger the entry and exit actions  .PermitReentry()
  //Use OnEntryFrom to indicate that when this state is triggered, a certain action is run. Here is an event with parameters specified here.  .OnEntryFrom(_setCalleeTrigger, callee =&gt; OnDialed(callee), "Caller number to call")
  .Permit(, );

_machine.Configure()
  //Define the substate  .SubstateOf()
  .Permit(, )
  .Permit(, );

_machine.Configure()
  //Execute the action when entering the state  .OnEntry(t =&gt; StartCallTimer())
  //Execute action after leaving the state  .OnExit(t =&gt; StopCallTimer())
  //The state does not change, but in response to some event, unlike PermitReentry, it will not trigger the entry and exit actions.  .InternalTransition(, t =&gt; OnMute())
  .InternalTransition(, t =&gt; OnUnmute())
  .InternalTransition&lt;int&gt;(_setVolumeTrigger, (volume, t) =&gt; OnSetVolume(volume))
  .Permit(, )
  .Permit(, )
  //Specify that when the same event occurs, it will decide to enter different states according to the different parameters of the event.  .PermitIf(_setCalleeTrigger, , callee =&gt; (callee))
  .PermitIf(_setCalleeTrigger, , callee =&gt; !(callee))
  //If this event occurs without defining this event, an exception will pop up.  This situation can be avoided by specifying that a certain type of event is ignored.  .Ignore();

//Of course you can also use this to avoid popping up the above exception._machine.OnUnhandledTrigger((state, trigger) =&gt; { });

//Asynchronous calls can be used, but FireAsync must be used when the event is triggered_machine.Configure()
  .OnEntryAsync(async () =&gt; await SendEmailToAssignee());

After configuring the transition between various states, the trigger event is the following.

public void Dialed(string callee)
{
  //Trigger with parameters  _machine.Fire(_setCalleeTrigger, callee);
}

public void Connected()
{
  //Trigger without parameters  _machine.Fire();
}

public async Task PhoneDestroy()
{
  //Async trigger  await _machine.FireAsync();
}

public string ToDotGraph()
{
  //Export DOT GRAPH  return (_machine.GetInfo());
}

External calls are simple:

("Prameela");
();
(2);
();

Just call events, and everything else will be carried out according to the actions we set, which is very automated.

Summarize

Stateless can implement state machines well, with a bit of an event-driven programming feel, but it is essentially different. The core of Stateless is the migration of various states.

Although Stateless is small and convenient, there are still many things that are not satisfactory (the official said that this is their own design goal, maintaining minimalism):

  • There is no saying of starting or stopping, and it is usually valid when created in the constructor.
  • Not thread-safe
  • Limited expansion

There is another(Address), this supports four different state machine implementations:

  • Passive State Machine: Synchronize single-threaded state transitions
  • Active State Machine: Synchronous multithreading state transitions
  • Async Passive State Machine: Asynchronous single-threaded state transitions
  • Async Active State Machine: Asynchronous multithreading state transitions

Among them, active is thread-safe. In addition, it also supports the persistence of state and events, and is highly scalable. The usage is similar, but there is a little difference in the configuration keywords, and you can translate the document by yourself.

The above is a detailed analysis of the Stateless C# state machine. For more information about Stateless C# stateless, please pay attention to my other related articles!