Recommend a high-performance state machine management solution

Recommend a high-performance state machine management solution

In actual software development, especially industrial software, every device has complex states and functional requirements for switching between states. In this case, how to manage states and switching between states, and functions under corresponding states. Control has become a very important issue.

最后更新 8/13/2025 8:25 AM
老码识途呀
预计阅读 15 分钟
分类
share .NET
标签
.NET C# industrial software state machine

In actual software development, especially industrial software, every device has complex states and functional requirements for switching between states. In this case, how to manage states and switching between states, and functions under corresponding states. Control has become a very important issue. If it is not handled well, this complicated state will become entangled and coupled "like noodles", a mess, which is really "endless cutting, but still messy." So how to solve this problem? Today, we use a simple small example to briefly describe how to manage and trigger state through Stateless components. It is only for learning and sharing use. If there are any shortcomings, please correct them.

What is the state mode?

Regarding the state mode and state machine, as follows:

  • State mode: "Allowing an object to change its behavior when its internal state changes. Object appears to have modified the class to which it belongs." (State Pattern: "Allow an object to alter its behavior when its internal state changes. The object will appear to change its class ".)。This definition is a bit abstract, but it can be understood as follows: the state owner delegates the change behavior to the state object, the state owner itself only owns the state (of course, the state object can also be abandoned), and the state object performs the change responsibility.

  • State machine: "Change the current state to a new state according to predetermined conditions according to the specified state flow chart and the currently executed action." The state machine has four elements, namely, current state, condition, action, and next state. Among them, the present state and condition are the "cause", and the action and secondary state are the "effect".

    • Current state-refers to the current state of the object
    • Condition-When a condition is met, the current object triggers an action
    • Action-Action to be performed after conditions are met
    • Secondary state-The new state of the current object after the condition is met. The secondary state is relative to the current state. Once triggered, it becomes the current state.
  • State transition diagram: "In UML modeling, it is often seen that it is used to describe all possible states of a specific object, as well as the transfers and changes between states caused by the occurrence of various events, and is also the prerequisite for configuring what the state machine should behave."

What is Stateless?

Stateless is a lightweight, high-performance state library that is implemented based on. Net Standard and can be used in both. NET Framework and. NET Core projects. It can easily help us implement the logic of state transition.

  • github:https://github.com/dotnet-state-machine/stateless

Example scenario description

This article mainly uses Stateless components to manage and trigger the following state. Currently, there is a device software that has the following states:

  1. Idle status, which means that no task is currently running and is in an idle state.
  2. Running status, which means that it is currently working and in a running state.
  3. Malfunction status, which means that there is an exception and is in a fault state.
  4. Recovery status means that it has been repaired, recovered from the fault, and is in a repair state.

It has a trigger action:

  1. Work means that work is currently started. It can be entered from Idle state or from Recovery state, and the program will enter Running state.
  2. Fatal means that an exception has been encountered. It can only be entered from the Running state, and the program will enter the Malfunction state.
  3. Repair means to repair exceptions. It can only be entered from the Malfunction state, and the program will enter the Recovery state.
  4. Finish means completion. It can be entered from the Running state or from the Recovery state. The program will enter the Idle state.

The above state (State) and trigger (Trigger) and the flow between them are shown in the following figure:

create a project

To demonstrate state changes, we created a WinForm application "Okcoder.Stateless.WinTool" and a public library "Okcoder.Stateless.Common" as follows:

Install Stateless components

In the Visual Studio 2022 development tool, installation can be carried out through the NuGet Package Manager. Right-click on the solution, open the right-click menu, then click "Manage NuGet Packages for Solutions" on the open NuGet package solution, search Stateless, and then install it. The current latest version is v5.18.0, as follows:

Define states and triggers

The state machine manages state changes and triggering events, so define states and triggers, which are enumerated types, and are defined according to the example scenario description. ToolState is as follows:

namespace Okcoder.Stateless.Common
{
    /// <summary>
    /// 工具状态
    /// </summary>
    public enum ToolState
    {
        Idle=0, //空闲状态
        Running=1, //运行状态
        Malfunction = 2, // 故障状态
        Recovery=3, //已恢复状态
    }
}

ToolTrigger is defined as follows:

namespace Okcoder.Stateless.Common
{
    public enum ToolTrigger
    {
        Work = 0, // 开始运转
        Fatal = 1, // 出异常
        Finish = 2, // 完成
        Repair=3, //维修
    }
}

defining interfaces

Define the IToolStateService interface, which represents the capabilities of the state machine, such as triggering actions, state judgment, state machine exporting DotGraph, etc., as follows:

namespace Okcoder.Stateless.Common
{
    /// <summary>
    /// 状态机接口
    /// </summary>
    public interface IToolStateService
    {
        /// <summary>
        /// 初始化状态
        /// </summary>
        void Init();

        /// <summary>
        /// 开始工作
        /// </summary>
        /// <returns></returns>
        ToolResult Work();

        /// <summary>
        /// 遇到严重错误
        /// </summary>
        /// <returns></returns>
        ToolResult Fatal();

        /// <summary>
        /// 维修
        /// </summary>
        /// <returns></returns>
        ToolResult Repair();

        /// <summary>
        /// 完成
        /// </summary>
        /// <returns></returns>
        ToolResult Finish();

        /// <summary>
        /// 是否是Idle状态
        /// </summary>
        /// <returns></returns>
        bool IsIdleState();

        /// <summary>
        /// 是否运行状态
        /// </summary>
        /// <returns></returns>
        bool IsRunningState();

        /// <summary>
        /// 是否故障状态
        /// </summary>
        /// <returns></returns>
        bool IsMalfunctionState();

        /// <summary>
        /// 是否恢复状态
        /// </summary>
        /// <returns></returns>
        bool IsRecoveryState();

        /// <summary>
        /// 获取当前状态
        /// </summary>
        /// <returns></returns>
        ToolState GetToolState();

        /// <summary>
        /// 状态机导出DotGraph
        /// </summary>
        /// <param name="path"></param>
        void ExportDotGraph(string path);
    }
}

Define the device work IToolWorkService interface, which means that the device runs work and performs operations. As follows:

namespace Okcoder.Stateless.Common
{
    /// <summary>
    /// 设备工作流程接口
    /// </summary>
    public interface IToolWorkService
    {
        /// <summary>
        /// 是否制造故障
        /// </summary>
        bool IsMakeFatal { get; set; }

        /// <summary>
        /// 开始工作
        /// </summary>
        void DoWork();

        /// <summary>
        /// 维修
        /// </summary>
        void Repair();
    }
}

achieve service

Implement the state machine service ToolStateService, which mainly uses Stateless components to manage state and trigger actions to switch states, as follows:

using Stateless;
using Stateless.Graph;
using System;
using System.IO;
using System.Text;

namespace Okcoder.Stateless.Common
{
    /// <summary>
    /// 状态机服务
    /// </summary>
    public class ToolStateService : IToolStateService
    {
        private Action<ToolState> toolAction;// 动作

        private StateMachine<ToolState, ToolTrigger> toolStateMachine;

        public ToolStateService(Action<ToolState> toolAction)
        {
            //定义状态机
            this.toolStateMachine = new StateMachine<ToolState, ToolTrigger>(ToolState.Idle);
            //配置每一个状态所允许的动作
            this.toolStateMachine.Configure(ToolState.Idle).Permit(ToolTrigger.Work, ToolState.Running);
            this.toolStateMachine.Configure(ToolState.Running).Permit(ToolTrigger.Fatal, ToolState.Malfunction).Permit(ToolTrigger.Finish, ToolState.Idle);
            this.toolStateMachine.Configure(ToolState.Malfunction).Permit(ToolTrigger.Repair, ToolState.Recovery);
            this.toolStateMachine.Configure(ToolState.Recovery).Permit(ToolTrigger.Work, ToolState.Running).Permit(ToolTrigger.Finish, ToolState.Idle);
            this.toolAction = toolAction;

        }

        /// <summary>
        /// 初始化状态
        /// </summary>
        public void Init()
        {
            this.toolAction(ToolState.Idle);
        }

        /// <summary>
        /// 响应触发动作
        /// </summary>
        /// <param name="trigger"></param>
        private ToolResult Fire(ToolTrigger trigger)
        {
            ToolResult toolResult = new ToolResult();
            try
            {
                if (!this.toolStateMachine.CanFire(trigger))
                {
                    toolResult.IsOk = false;
                    toolResult.Desc = $"当前状态是{this.toolStateMachine.State},不允许执行{trigger}操作";
                }
                else
                {
                    this.toolStateMachine.Fire(trigger);
                    toolResult.IsOk = true;
                    toolResult.Desc = "Ok";
                    DoAction();

                }
            }
            catch (InvalidOperationException ex)
            {
                toolResult.IsOk = false;
                toolResult.Desc = ex.Message;
            }
            return toolResult;
        }

        /// <summary>
        /// 判断是否在状态
        /// </summary>
        /// <param name="toolState"></param>
        /// <returns></returns>
        private bool IsInState(ToolState toolState)
        {
            return this.toolStateMachine.IsInState(toolState);
        }

        /// <summary>
        /// 事件通知
        /// </summary>
        private void DoAction()
        {
            if (this.toolAction != null)
            {
                this.toolAction(this.toolStateMachine.State);
            }
        }

        /// <summary>
        /// 开始工作
        /// </summary>
        public ToolResult Work()
        {
            return this.Fire(ToolTrigger.Work);
        }

        /// <summary>
        /// 遇到严重错误
        /// </summary>
        public ToolResult Fatal()
        {
            return this.Fire(ToolTrigger.Fatal);
        }

        /// <summary>
        /// 维修
        /// </summary>
        public ToolResult Repair()
        {
            return this.Fire(ToolTrigger.Repair);
        }

        /// <summary>
        /// 完成
        /// </summary>
        public ToolResult Finish()
        {
            return this.Fire(ToolTrigger.Finish);
        }

        /// <summary>
        /// 是否是Idle状态
        /// </summary>
        /// <returns></returns>
        public bool IsIdleState()
        {
            return this.IsInState(ToolState.Idle);
        }

        /// <summary>
        /// 是否运行状态
        /// </summary>
        /// <returns></returns>
        public bool IsRunningState()
        {
            return this.IsInState(ToolState.Running);
        }

        /// <summary>
        /// 是否故障状态
        /// </summary>
        /// <returns></returns>
        public bool IsMalfunctionState()
        {
            return this.IsInState(ToolState.Malfunction);
        }

        /// <summary>
        /// 是否恢复状态
        /// </summary>
        /// <returns></returns>
        public bool IsRecoveryState()
        {
            return this.IsInState(ToolState.Recovery);
        }

        public ToolState GetToolState()
        {
            return this.toolStateMachine.State;
        }

        /// <summary>
        /// 状态机导出DotGraph
        /// </summary>
        /// <param name="path"></param>
        public void ExportDotGraph(string path)
        {
            var info = this.toolStateMachine.GetInfo();
            string graph = UmlDotGraph.Format(info);
            using (FileStream stream = File.OpenWrite(path))
            {
                using (StreamWriter writer = new StreamWriter(stream))
                {
                    writer.Write(graph);
                    writer.Close();
                }
                stream.Close();
            }
        }

    }
}

StateMachine is the core class of the state machine. It has two generic parameters, representing ToolState and ToolTrigger respectively. The initial state is passed in the constructor. In this example, you can pass in ToolStage.Idle.

StateMachine configures the allowed states through the Configure method of the object instance and configures the new states that are allowed to be converted through Trigger through the current state through the Permit method.

After the configuration is completed, the Fire method is used to respond to the triggering action, and the IsInState method is used to determine whether it is currently in the specified state.

在状态机服务类构造函数中,接收一个Action<string>的委托方法,用于输出状态到UI层,当然也可以采用其他事件订阅发布的解耦方法。

The return value of ToolResult represents the interface returned by the state machine after execution. It is a class, as follows:

namespace Okcoder.Stateless.Common
{
    /// <summary>
    /// 工具执行结果
    /// </summary>
    public class ToolResult
    {
        public bool IsOk { get; set; }

        public string Desc { get; set; }
    }
}

To implement the work service ToolWorkService, it mainly simulates the operation of equipment tools. When starting work, output text to the UI page. If there is an exception, stop; if there is no exception, the operation is completed, as follows:

namespace Okcoder.Stateless.Common
{
    /// <summary>
    /// 设备工作服务
    /// </summary>
    public class ToolWorkService:IToolWorkService
    {
        private Action<string> _progress;

        private IToolStateService _toolStateService;

        private bool _isMakeFatal=false;

        public bool IsMakeFatal
        {
            get { return _isMakeFatal; }
            set { _isMakeFatal = value; }
        }

        public ToolWorkService(IToolStateService toolStateService, Action<string> progress,bool isMakeFatal=false)
        {
            _progress = progress;
            _toolStateService = toolStateService;
            _isMakeFatal = isMakeFatal;
        }

        public void DoWork()
        {
            BeginWork();
            Work();
            Finish();
        }

        public void Repair()
        {
            var result = this._toolStateService.Repair();
            if (result.IsOk)
            {
                OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 故障被修好了");
            }
            else
            {
                OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 故障没有被修好");
            }

        }

        /// <summary>
        /// 开始工作
        /// </summary>
        private void BeginWork()
        {
            var result = this._toolStateService.Work();
            if (result.IsOk)
            {
                OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 开始工作了");
            }
            else
            {
                OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 开始工作失败了");
            }

        }

        /// <summary>
        /// 工作过程
        /// </summary>
        private void Work()
        {
            for (int i = 0; i < 30; i++)
            {
                if (_isMakeFatal)
                {
                    if (i > 10)
                    {
                        MarkFatal();// 运行一段时间,报故障
                        break;
                    }
                }
                OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 当前正在工作 - {i}");
                Thread.Sleep(300);
            }
        }

        /// <summary>
        /// 完成工作
        /// </summary>
        private void Finish()
        {
            var result = this._toolStateService.Finish();
            if (result.IsOk)
            {
                OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 完成工作了");
            }
            else
            {
                OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {result.Desc}");
            }
        }

        private void MarkFatal()
        {
            var result = this._toolStateService.Fatal();
            if (result.IsOk)
            {
                OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 哎呀,出错了呢?");
            }
            else
            {
                OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 制造故障失败");
            }

        }

        /// <summary>
        /// 输出信息
        /// </summary>
        /// <param name="msg"></param>
        private void OutPutInfo(string msg)
        {
            if (_progress != null)
            {
                _progress(msg);
            }
        }
    }
}

其中构造函数接收一个Action<string>的委托,用于向UI输出信息,当然也可以是其他的形式。

UI call

In the Okcoder.Stateless.WinTool project, create the FrmMain page, define object instances of IToolStateService and IToolWorkService in the constructor, and initialize them in the Load method.

When the user clicks Work, the Work method of the IToolWorkService starts to be called, and during execution, information will be output to the UI page.

There is a check box on the UI page to set whether to trigger an exception. You can simulate an exception. If an exception occurs, click the Repair button to repair it.

When performing each action, you can see different state changes. As follows:

using Okcoder.Stateless.Common;

namespace Okcoder.Stateless.WinTool
{
    public partial class FrmMain : Form
    {
        private IToolStateService toolStateService;

        private IToolWorkService toolWorkService;

        public FrmMain()
        {
            InitializeComponent();
            this.toolStateService = new ToolStateService(ToolStateAction);
            this.toolWorkService = new ToolWorkService(toolStateService, ToolWorkProcessAction, this.chkFatal.Checked);
        }

        private void FrmMain_Load(object sender, EventArgs e)
        {
            this.toolStateService.Init();
        }

        /// <summary>
        /// 开始工作
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnWork_Click(object sender, EventArgs e)
        {
            this.txtInfo.Text = string.Empty;
            this.toolWorkService.IsMakeFatal = this.chkFatal.Checked;
            Task.Run(() =>
            {
                this.toolWorkService.DoWork();
            });
        }

        /// <summary>
        /// 维修
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRepair_Click(object sender, EventArgs e)
        {
            this.toolWorkService.Repair();
        }

        /// <summary>
        /// 导出状态机DotGraph文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnExport_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            saveFileDialog.DefaultExt = "txt";
            saveFileDialog.Filter = "txt文件|*.txt";
            saveFileDialog.FileName = "Okcoder.txt";
            if (saveFileDialog.ShowDialog() == DialogResult.OK)
            {
                var filePath = saveFileDialog.FileName;
                this.toolStateService.ExportDotGraph(filePath);
                MessageBox.Show("导出成功");
            }
        }

        private void ToolStateAction(ToolState toolState)
        {
            this.Invoke(() =>
            {
                this.lblState.Text = toolState.ToString();
                switch (toolState)
                {
                    case ToolState.Idle:
                    case ToolState.Recovery:
                        this.btnWork.Enabled = true;
                        this.btnRepair.Enabled = false;
                        this.lblState.ForeColor = Color.Black;
                        break;
                    case ToolState.Running:
                        this.btnWork.Enabled = false;
                        this.btnRepair.Enabled = false;
                        this.lblState.ForeColor = Color.Goldenrod;
                        break;
                    case ToolState.Malfunction:
                        this.btnWork.Enabled = false;
                        this.btnRepair.Enabled = true;
                        this.lblState.ForeColor = Color.Red;
                        break;
                }
                this.Refresh();
            });
        }

        private void ToolWorkProcessAction(string msg)
        {
            this.Invoke(() =>
            {
                this.txtInfo.AppendText(msg + "\r\n");
            });

        }
    }
}

example shows

After the above steps, run the program, and then click the Work button. When there is no fault by default, it is as follows:

When fault is checked and a fault is simulated, as follows:

The above is the entire content of "Recommended a High-Performance State Machine Management Solution". For more details, please refer to the official documentation. I hope to learn together and make progress together.

Learn programming, start by paying attention to "Old Code Understanding Ways" and share more articles with you!!!

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 4/22/2026

Support for. NET by operating system versions (250707 update)

Use virtual machines and test machines to test the support of each version of the operating system for. NET. After installing the operating system, it is passed by measuring the corresponding running time of the installation and being able to run the Stardust Agent.

继续阅读
同分类 / 同标签 2/7/2026

Summary of experience in using AOT

From the very beginning of project creation, you should develop a good habit of conducting AOT release testing in a timely manner whenever new features are added or newer syntax is used.

继续阅读