推薦一款高性能狀態機管理解決方案

推薦一款高性能狀態機管理解決方案

在實際軟體開發中,尤其是工業軟體,每一款設備都有複雜的狀態以及狀態之間的切換的功能需求,在這種情況下,如何管理狀態以及狀態之間切換,和對應狀態下的功能控制,成為非常重要的一個問題。

最后更新 2025/8/13 上午8:25
老码识途呀
预计阅读 13 分钟
分类
分享 .NET
标签
.NET C# 工業軟體 狀態機

在實際軟體開發中,尤其是工業軟體,每一款設備都有複雜的狀態以及狀態之間的切換的功能需求,在這種情況下,如何管理狀態以及狀態之間切換,和對應狀態下的功能控制,成為非常重要的一個問題。如果處理不好,那這種繁複的狀態將成為“像麵條一樣”纏繞耦合,一團亂麻,真的就是“剪不斷,理還亂”。那如何解決這個問題呢?今天我們以一篇簡單的小例子,簡述如何通過stateless組件,完成狀態的管理和觸發,僅供學習分享使用,如有不足之處,還請指正。

什麼是狀態模式?

關於狀態模式和狀態機,如下所示:

  • 狀態模式:"允許一個對象在其內部狀態改變時改變它的行為。對象看起來似乎修改了它所屬的類 "。(state pattern: "allow an object to alter its behavior when its internal state changes. the object will appear to change its class ".)。對於這個定義,有點抽象,變通理解一下可以這麼理解:狀態擁有者將變更行為委託給狀態對象,狀態擁有者本身只擁有狀態(當然也可以拋棄狀態對象),狀態對象履行變更職責。

  • 狀態機:"依照指定的狀態流程圖,根據當前執行的動作,將當前狀態按照預定的條件變更到新的狀態 "。狀態機有4個要素,即現態、條件、動作、次態。其中,現態和條件是“因”, 動作和次態是“果”。

    • 現態 - 是指當前對象的狀態
    • 條件 - 當一個條件滿足時,當前對象會觸發一個動作
    • 動作 - 條件滿足之後,執行的動作
    • 次態 - 條件滿足之後,當前對象的新狀態。次態是相對現態而言的,次態一旦觸發,就變成了現態
  • 狀態遷移圖:"在uml建模中,常常可見,用來描述一個特定的對象所有可能的狀態,以及由於各種事件的發生而引起的狀態之間的轉移和變化,也是配置狀態機按照何種行徑的前提 "。

什麼是stateless?

stateless是一個輕量級、高性能的狀態機庫,它基於.net standard實現,在.net framework和.net core項目中都可以使用,能夠輕鬆地幫助我們實現狀態轉換的邏輯。

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

實例場景說明

本文主要通過stateless組件實現如下一個狀態的管理和觸發,當前有一個設備軟體,它擁有如下幾個狀態(state):

  1. idle狀態,表示當前沒有運行任務,處於空閒狀態。
  2. running狀態,表示當前正在工作,處於運行狀態。
  3. malfunction狀態,表示當前有異常,處於故障狀態。
  4. recovery狀態,表示經過維修,從故障中恢復了,處於修復狀態。

它擁有觸發動作(trigger):

  1. work,表示當前開始工作,它可以由idle狀態進入,也可以從recovery狀態進入,程式會進入running狀態。
  2. fatal,表示遇到異常,它只可以由running狀態進入,程式會進入malfunction狀態。
  3. repair,表示修復異常,它只可以由malfunction狀態進入,程式會進入recovery狀態。
  4. finish,表示完成,它可以由running狀態進入,也可以由recovery狀態進入,程式會進入idle狀態。

上述狀態(state)和觸發動作(trigger)以及之間的流轉,如下圖所示:

創建項目

為了演示狀態變化,我們創建一個winform應用程式“okcoder.stateless.wintool”和一個公共庫“okcoder.stateless.common”,如下所示:

安裝stateless組件

在visual studio 2022開發工具中,可以通過nuget包管理器進行安裝。在解決方案右鍵,打開右鍵菜單,然後點擊“管理解決方案的nuget程式包”在打開的nuget包解決方案,搜索stateless,然後安裝即可。當前最新版本為v5.18.0,如下所示:

定義狀態和觸發器

狀態機管理狀態的變化和觸發事件,所以定義狀態和觸發器,它們是枚舉類型,根據實例場景說明進行定義toolstate如下所示:

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

tooltrigger定義如下所示:

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

定義接口

定義itoolstateservice接口,它表示狀態機所具備的能力,如觸發動作,狀態判斷,狀態機導出dotgraph等,如下所示:

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);
    }
}

定義設備工作itoolworkservice接口,它表示設備運行工作,執行操作。如下所示:

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

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

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

實現服務

實現狀態機服務toolstateservice,它主要應用stateless組件實現狀態的管理和觸發動作切換狀態,如下所示:

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是狀態機的核心類,它有兩個泛型參數,分別表示toolstate和tooltrigger,構造函數中傳入初始狀態,在本示例中,傳入toolstage.idle即可。

statemachine通過對象實例的configure方法配置允許的狀態及通過permit方法配置當前狀態允許通過trigger轉換的新的狀態。

配置完成後,通過fire方法響應觸發動作,通過isinstate方法判斷當前是否處於指定的狀態。

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

其中toolresult返回值,表示狀態機執行後返回的接口,它是一個類,如下所示:

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

        public string Desc { get; set; }
    }
}

實現工作服務toolworkservice,主要是模擬設備工具運行,當開始工作時,輸出文本到ui頁面上,如果有異常,則停止;若無異常,則運行完成,如下所示:

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調用

在okcoder.stateless.wintool項目中,創建frmmain頁面,在構造函數中定義itoolstateservice和itoolworkservice的對象實例,並且在load方法中初始化。

當用戶點擊work時,開始調用itoolworkservice的work方法,在執行過程中,會輸出信息到ui頁面。

在ui頁面有一個複選框,用於設置是否觸發異常,可以模擬有異常的場景,如果出現異常,則點擊repair按鈕進行修復。

在執行各個動作時,可以看到不同的狀態變化。如下所示:

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");
            });

        }
    }
}

實例演示

經過上述步驟,運行程式,然後點擊work按鈕,默認無故障時如下所示:

當勾選故障,模擬出現故障時,如下所示:

以上就是《推薦一款高性能狀態機管理解決方案》的全部內容,關於更多詳細內容,可參考官方文檔。希望能夠一起學習,共同進步。

學習編程,從關注【老碼識途】開始,為大家分享更多文章!!!

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 2025/3/18

(7)從護士到c#開發者--面向對象編程基礎

作為一名從護理行業轉行的程式設計師,我將分享如何通過醫護工作經驗來理解面向對象編程的概念。本文將居間類、對象、屬性、方法等面向對象的核心概念,並結合醫療保健領域的實例來加深理解。

继续阅读
同分类 / 同标签 2025/2/25

net 10 preview 1發布

今天.net 10 preview 1發布了,我第一時間下載,升級了avalonia ui項目和博客網站,前者功能測試及aot發布正常,後者調試正常,docker暫時未成功

继续阅读