资讯动态

Stateless 使用指南:在 C 中直接构建状态机与轻量级工作流

发布时间:2026/10/15 1:56:37 来源:尧图企业网站定制
后端工作流自动化【免费下载链接】statelessA simple library for creating state machines in C# code项目地址https://gitcode.com/gh_mirrors/st/stateless点击查看免费下载本文是 Stateless 状态机库的完整使用指南。Stateless 是一个在 .NET 代码中直接创建状态机State Machine与轻量级状态机工作流State Machine-based Workflows的库本文以项目文档 src/Stateless/docs/README.md 为核心脉络并结合 src/Stateless 源码、example 示例与 test 测试展开讲解。读完本文你将掌握用StateMachineTState, TTrigger在一行代码内创建状态机、通过链式 API 配置层次化状态 / 守卫子句 / 参数化触发器 / 异步动作、接入 ORM 与 JSON 序列化的外部状态存储、订阅与注销状态转换事件以及把运行中的状态机导出为 DOT 与 Mermaid 图并集成到文档与可视化工具中。初识 Stateless在 .NET 代码中直接定义状态机Stateless 的核心用法非常直观用泛型类型StateMachineTState, TTrigger实例化状态机其中TState是状态的类型TTrigger是触发事件的类型二者可以是任意 .NET 类型——枚举、字符串、整数、数字均可。文档首页的电话通话示例完整演示了这一点var phoneCall new StateMachineState, Trigger(State.OffHook); phoneCall.Configure(State.OffHook) .Permit(Trigger.CallDialled, State.Ringing); phoneCall.Configure(State.Connected) .OnEntry(t StartCallTimer()) .OnExit(t StopCallTimer()) .InternalTransition(Trigger.MuteMicrophone, t OnMute()) .InternalTransition(Trigger.UnmuteMicrophone, t OnUnmute()) .InternalTransitionint(_setVolumeTrigger, (volume, t) OnSetVolume(volume)) .Permit(Trigger.LeftMessage, State.OffHook) .Permit(Trigger.PlacedOnHold, State.OnHold); // ... phoneCall.Fire(Trigger.CallDialled); Assert.AreEqual(State.Ringing, phoneCall.State);流程是Configure(state)打开某个状态的配置链式调用Permit(trigger, destinationState)声明在源状态收到某触发后迁移到目标状态最后用Fire(trigger)触发事件由State属性读取当前状态。项目说明指出该示例的灵感来源于 Simple State Machine已归档项目。安装与目标框架仓库中 src/Stateless/Stateless.csproj 定义了核心库的项目元数据程序集名为StatelessNuGet 版本前缀为5.20.1许可证为 Apache-2.0。当前版本通过多目标框架同时面向目标框架说明netstandard2.0.NET Standard 2.0可被绝大多数现代 .NET 平台消费net462.NET Framework 4.6.2 及更高版本net8.0/net9.0/net10.0现代 .NET 长期支持与最新版本文档明确说明Stateless 运行在 .NET runtime 4.0 及以上通过上述目标框架几乎覆盖所有现代 .NET 平台构建解决方案需要 Visual Studio 2017 或更高版本。在你的 .NET 项目中直接添加对Stateless包的引用即可使用。核心特性总览Stateless 支持绝大多数标准状态机构造通用泛型支持状态与触发可以是任意 .NET 类型数字、字符串、枚举等层次化状态Hierarchical States子状态继承父状态的行为状态的进入/退出动作Entry/Exit Actions进入或离开状态时自动执行回调守卫子句Guard Clauses支持带条件的转换多个候选转换之间按守卫求值选择内省Introspection运行时查询当前允许的触发与完整状态配置。同时提供若干实用扩展外部状态存储例如把状态存到由 ORM 跟踪的属性中参数化触发Parameterised Triggers触发携带强类型参数可重入状态Reentrant States状态可转换回自身并重新执行进出动作导出 DOT 与 Mermaid 图把状态机渲染为可视化图形。基础用法配置、迁移与触发定义状态与触发推荐用枚举表达状态与触发可获得编译期检查和良好的可读性。以 example/TelephoneCallExample/PhoneCall.cs 为参考enum State { OffHook, Ringing, Connected, OnHold, PhoneDestroyed } enum Trigger { CallDialed, CallConnected, LeftMessage, PlacedOnHold, TakenOffHold, PhoneHurledAgainstWall, MuteMicrophone, UnmuteMicrophone, SetVolume }Configure / Permit / FireConfigure(TState state)返回一个 StateConfiguration 对象其实现位于 src/Stateless/StateConfiguration.cs#L42-L46 的Permit方法所有 API 都以链式调用方式编排_machine.Configure(State.OffHook) .Permit(Trigger.CallDialed, State.Ringing); _machine.Configure(State.Ringing) .Permit(Trigger.CallConnected, State.Connected); _machine.Configure(State.Connected) .Permit(Trigger.LeftMessage, State.OffHook) .Permit(Trigger.PlacedOnHold, State.OnHold);调用Fire(trigger)时状态机根据当前状态的配置寻找处理程序并执行转换State属性始终返回精确的当前状态。注意Permit(trigger, destinationState)会强制校验目标状态不等于源状态防止无意中的自环自环场景应改用文档后文介绍的PermitReentry/PermitDynamic。触发模式Immediate 与 Queued从 src/Stateless/StateMachine.cs#L12-L18 可以看到触发模式由FiringMode枚举控制FiringMode.Immediate立即处理触发。无 run-to-completion 保证若在OnEntry等回调中再次Fire会立即嵌套执行FiringMode.Queued默认推荐触发先入队按 FIFO 顺序逐个处理保证 run-to-completion。源码中StateMachine的两个公开构造函数src/Stateless/StateMachine.cs#L51-L61默认都采用FiringMode.Queued。两个模式的调度逻辑分别对应InternalFireQueued与InternalFireOnesrc/Stateless/StateMachine.cs#L336-L467。可通过构造函数第三个参数显式指定模式var sm new StateMachineState, Trigger(State.OffHook, FiringMode.Immediate);未处理触发的默认行为默认情况下在当前状态没有为某触发配置任何允许的转换时Fire会抛出InvalidOperationException。该默认动作实现在 src/Stateless/StateMachine.cs#L788-L800 的DefaultUnhandledTriggerAction中若存在未满足的守卫条件异常信息会列出这些守卫否则提示该触发在当前状态不被允许。后文忽略转换与可重入状态一节介绍用OnUnhandledTrigger覆盖此默认行为。层次化状态Hierarchical States子状态通过SubstateOf(superstate)声明。下面的例子中OnHold是Connected的子状态因此处于OnHold的通话依然是已连接状态phoneCall.Configure(State.OnHold) .SubstateOf(State.Connected) .Permit(Trigger.TakenOffHold, State.Connected) .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);与返回精确当前状态的StateMachine.State属性不同IsInState(State)方法会把子状态纳入考量若状态机当前处于OnHold则IsInState(State.Connected)同样返回true。其实现位于 src/Stateless/StateMachine.cs#L578-L581本质是调用当前表示的IsIncludedIn沿父状态链向上判断。从源码看src/Stateless/StateConfiguration.cs#L1128-L1158SubstateOf还会做防御性校验把状态配置为自身的子状态、或构成嵌套循环如 A 是 B 的子状态且 B 又是 A 的子状态时会抛出ArgumentException。子状态的另一个重要行为是触发查找沿父状态链上溯在子状态中找不到某触发的处理程序时会在父状态superstate中继续查找。这允许子状态继承父状态的转换同时子状态可以通过重新指定相同触发来覆盖父状态的行为。进入/退出动作Entry/Exit Actions在电话示例中StartCallTimer()在通话接通时执行StopCallTimer()在通话结束挂断或摔手机时执行_machine.Configure(State.Connected) .OnEntry(t StartCallTimer()) .OnExit(t StopCallTimer());由于OnHold是Connected的子状态通话在Connected与OnHold之间往返时不会反复执行StartCallTimer()/StopCallTimer()——只有当真正离开Connected状态树时退出/进入动作才会被触发。进入/退出动作的处理函数可以接收一个Transition类型参数该对象描述了触发、源状态与目标状态。Transition类的定义在 src/Stateless/Transition.cs 中包含Source、Destination、Trigger、Parameters四个只读属性以及指示是否为自环重入的IsReentry属性构造函数保证Parameters永不为 null无参数触发时为空数组。如果需要仅当通过某个特定触发进入状态时才执行动作使用OnEntryFrom(trigger, action)实现见 src/Stateless/StateConfiguration.cs#L892-L1071这在参数化触发器一节会有实际用例。内部转换Internal Transitions有时某个触发需要被处理但状态不应改变——这就是内部转换使用InternalTransition_machine.Configure(State.Connected) .InternalTransition(Trigger.MuteMicrophone, t OnMute()) .InternalTransition(Trigger.UnmuteMicrophone, t OnUnmute());内部转换不触发 Exit / Entry 动作也不改变状态只执行关联的动作。从 src/Stateless/StateConfiguration.cs#L54-L57 的实现看它本质是注册一个InternalTriggerBehaviour在处理时src/Stateless/StateMachine.cs#L457-L463构造Transition(source, source, trigger, args)并仅执行内部动作。内部转换同样支持携带参数与守卫条件。电话示例中设置音量是带int参数的内部转换_internalTransitionint(_setVolumeTrigger, (volume, t) OnSetVolume(volume));重载形式包括InternalTransitionTArg0、InternalTransitionTArg0, TArg1、InternalTransitionTArg0, TArg1, TArg2以及对应的InternalTransitionIf(...)带守卫版本可附加守卫描述字符串详见 src/Stateless/StateConfiguration.cs#L126-L283。初始状态转换Initial State Transitions子状态可以被标记为初始状态initial state当状态机进入超状态superstate时会自动继续进入该初始子状态sm.Configure(State.B) .InitialTransition(State.C); sm.Configure(State.C) .SubstateOf(State.B);InitialTransition的实现见 src/Stateless/StateConfiguration.cs#L1817-L1822。从源码的EnterState逻辑src/Stateless/StateMachine.cs#L529-L545可以看到两点重要约束InitialTransition的目标必须是该状态的子状态否则抛出InvalidOperationExceptionThe target ... for the initial transition is not a substate初始转换同样会触发OnTransitioned事件以InitialTransition形式通知监听者并递归进入更深层的初始子状态。文档特别指出一个设计局限由于 Stateless 的内部结构状态机本身并不知道自己何时启动因此无法以传统方式处理初始转换。绕过该局限的推荐做法是添加一个哑初始状态dummy initial state然后借助Activate()来启动状态机sm.Configure(InitialState) .OnActivate(() sm.Fire(LetsGo)) .Permit(LetsGo, StateA)外部状态存储External State StorageStateless 被设计为可嵌入各种应用模型某些 ORM 对映射数据的存放位置有要求UI 框架常要求把状态存到特殊的可绑定bindable属性中。为此StateMachine构造函数可以接受一对函数参数分别用于读取和写入状态值var stateMachine new StateMachineState, Trigger( () myState.Value, s myState.Value s);该构造函数src/Stateless/StateMachine.cs#L46-L76要求stateAccessor与stateMutator均非 null并把初始状态读取为stateAccessor()的返回值。此时状态机本体不持有任何内部状态这就是Stateless名称的由来详见文末State属性的 getter/settersrc/Stateless/StateMachine.cs#L111-L121直接委托给这两个函数。文档提到的 JsonExample 解决方案位于 example/JsonExample是外部状态存储的典型实践。其 Member.cs 把会员状态MembershipStateInactive/Active/Terminated通过构造函数注入到状态机中并提供ToJson()/FromJson()完成序列化往返[JsonConstructor] private Member(string state, string name) { var memberState (MembershipState) Enum.Parse(typeof(MembershipState), state); _stateMachine new StateMachineMembershipState, MemberTriggers(memberState); Name name; ConfigureStateMachine(); }Program.cs 演示了完整流程从 JSON 反序列化重建会员 →Suspend()/Reactivate()/Terminate()驱动状态迁移 → 再序列化为 JSON 并反序列化验证对象相等。这意味着状态的持久化位置完全由你掌控状态机只负责转换规则。激活与停用Activation / Deactivation有时在存储对象状态之前、以及在恢复对象状态之后需要执行某些代码。为此提供Deactivate与ActivateActivate()执行当前状态的激活activate动作例如恢复外部资源Deactivate()执行当前状态的停用deactivate动作例如保存前释放或提交资源。文档建议激活只在正常操作开始前调用一次停用只在状态存储前调用一次。对应 API 为OnActivate(() ...)与OnDeactivate(() ...)异步版本为ActivateAsync()/DeactivateAsync()src/Stateless/StateMachine.Async.cs#L16-L31。激活/停用动作是幂等的对同一状态的重复激活不会导致回调重复执行。测试 test/Stateless.Tests/ActiveStatesFixture.cs 专门验证了这一点WhenActivateIsIdempotent并验证了执行顺序——激活时从最深子状态开始、停用时从当前状态逐级向外WhenActivate断言期望顺序为ActivatedC, ActivatedAWhenDeactivate断言为DeactivatedA, DeactivatedC。此外激活/停用不会触发转换事件测试中注册了OnTransitioned回调并断言其未被调用。内省Introspection状态机提供两类内省能力1. 当前可触发的触发列表StateMachine.PermittedTriggers属性返回在当前状态下可被成功触发的触发集合。需要说明的是源码 src/Stateless/StateMachine.cs#L123-L145 中该属性及GetPermittedTriggers(...)已被标记为[Obsolete]出于兼容保留推荐使用异步版本PermittedTriggersAsync/GetPermittedTriggersAsync(params object[] args)以及能返回带参数信息的GetDetailedPermittedTriggers(...)。2. 状态配置快照调用StateMachine.GetInfo()可获取一个 StateMachineInfo 对象它暴露InitialState、States含各状态的进入/退出动作、转换、守卫条件等、StateType与TriggerType。该对象是后文 DOT / Mermaid 图形导出的输入也可用于自省与调试。GetInfo()的实现位于 src/Stateless/StateMachine.cs#L158-L184。此外CanFire(trigger)可预判某触发在当前状态是否可触发其带out ICollectionstring unmetGuards的重载src/Stateless/StateMachine.cs#L663-L720还能返回未满足的守卫条件描述便于诊断为什么不能触发。示例项目 example/AlarmExample/Program.cs 展示了如何结合CanFire列出当前所有可触发命令。守卫子句Guard Clauses状态机会根据守卫子句在多个转换候选中选择目标。典型场景是同一个触发按条件迁移到不同状态phoneCall.Configure(State.OffHook) .PermitIf(Trigger.CallDialled, State.Ringing, () IsValidNumber) .PermitIf(Trigger.CallDialled, State.Beeping, () !IsValidNumber);异步版本phoneCall.Configure(State.OffHook) .PermitIfAsync(Trigger.CallDialled, State.Ringing, async () await IsValidNumber()) .PermitIfAsync(Trigger.CallDialled, State.Beeping, async () !await IsValidNumber());使用守卫子句时必须遵守两条规则文档明示同一状态内的守卫必须互斥——同一触发下多个守卫不能同时为真否则行为未定义子状态可以覆盖respecify父状态的转换但不能禁止父状态允许的转换——上溯查找机制保证了这一点。守卫会在触发被Fire的每一次求值因此守卫**必须是无副作用side effect free**的纯判断函数。从源码看守卫由TransitionGuard承载见 src/Stateless/TransitionGuard.csPermitIf的各重载src/Stateless/StateConfiguration.cs#L295-L461支持简单守卫Funcbool可附带guardDescription描述字符串多守卫组合params TupleFuncbool, string[] guards带参数的守卫FuncTArg0, bool、FuncTArg0, TArg1, bool、FuncTArg0, TArg1, TArg2, bool。守卫描述会进入StateMachineInfoGuardConditionsMethodDescriptions见 src/Stateless/Reflection/TransitionInfo.cs从而出现在 DOT/Mermaid 导出图与未满足守卫异常信息中这也是CanFire(out unmetGuards)数据来源。参数化触发器Parameterised Triggers强类型参数可以绑定到触发上。先通过SetTriggerParametersTArg0(trigger)声明参数类型得到TriggerWithParametersTArg0对象再在配置与触发时使用它var assignTrigger stateMachine.SetTriggerParametersstring(Trigger.Assign); stateMachine.Configure(State.Assigned) .OnEntryFrom(assignTrigger, email OnAssigned(email)); stateMachine.Fire(assignTrigger, joeexample.com);参数化触发在源码层面由TriggerWithParameters家族表示src/Stateless/TriggerWithParameters.csSetTriggerParameters的重载位于 src/Stateless/StateMachine.cs#L245-L250非泛型、按参数类型数组与 src/Stateless/StateMachine.cs#L741-L777泛型 1~3 个参数。Fire时src/Stateless/StateMachine.cs#L232-L307会先经ValidateParameters校验参数个数与类型再执行内部触发。触发参数还可以用于动态选择目标状态见下节PermitDynamic()也可配合OnEntryFrom/InternalTransition使用。完整的带参数触发示例可参考 example/TelephoneCallExample/PhoneCall.cs 中的_setVolumeTriggerint参数与_setCalleeTriggerstring参数用于在Ringing状态记录被叫号码。忽略转换与可重入状态Ignored Transitions and Reentrant StatesStateless 的默认策略是一切转换必须显式定义在当前状态触发一个没有允许转换的触发会抛出异常从而防止意外状态变更。要吞掉某状态下的特定触发使用Ignore(TTrigger)phoneCall.Configure(State.Connected) .Ignore(Trigger.CallDialled);另一种思路是把状态标记为可重入reentrant。可重入状态可以转换回自身此时该状态的退出与进入动作都会执行——非常适合需要重置或重新初始化的事件stateMachine.Configure(State.Assigned) .PermitReentry(Trigger.Assigned) .OnEntry(() SendEmailToAssignee());可重入同样支持带守卫的同步与异步条件版本stateMachine.Configure(State.Assigned) .PermitReentryIf(Trigger.Assigned, () ShouldSendEmailAgain()) .OnEntry(() SendEmailToAssignee()); stateMachine.Configure(State.Assigned) .PermitReentryIfAsync(Trigger.Assigned, async () await ShouldSendEmailAgain()) .OnEntry(() SendEmailToAssignee());PermitReentry/PermitReentryIf的实现见 src/Stateless/StateConfiguration.cs#L473-L645。需要注意源码注释也明确重入只作用于当前状态本身不会重新执行超状态superstate的动作也不会触发超状态与子状态之间的转换动作。默认情况下触发必须被显式忽略。要覆盖未处理触发即抛异常的默认行为用OnUnhandledTrigger注册兜底回调stateMachine.OnUnhandledTrigger((state, trigger) { });OnUnhandledTrigger有两种重载src/Stateless/StateMachine.cs#L555-L570ActionTState, TTrigger与带ICollectionstring unmetGuards的ActionTState, TTrigger, ICollectionstring后者能拿到未满足的守卫描述列表异步版本为OnUnhandledTriggerAsyncsrc/Stateless/StateMachine.Async.cs#L411-L415。示例 example/AlarmExample/Program.cs 就是靠捕获InvalidOperationException来提示当前状态不允许该命令。动态状态转换Dynamic State Transitions动态状态转换允许在运行时根据触发参数或其他逻辑决定目标状态stateMachine.Configure(State.Start) .PermitDynamic(Trigger.CheckScore, () score 10 ? State.LowScore : State.HighScore);PermitDynamic的实现见 src/Stateless/StateConfiguration.cs#L1172-L1222其重载支持 1~3 个触发参数FuncTArg0, TState等并可附带destinationStateSelectorDescription描述与possibleDestinationStates候选目标列表用于图形导出。带守卫版本为PermitDynamicIfsrc/Stateless/StateConfiguration.cs#L1308-L1758。动态转换的一个精妙之处当动态计算出的目标状态与当前状态相同时它实际上变成一次重入转换状态的退出与进入动作会被执行——非常适合按状态刷新/重置的场景stateMachine.Configure(State.Waiting) .OnEntry(() Console.WriteLine($Elapsed time: {elapsed} seconds...)) .PermitDynamic(Trigger.CheckStatus, () ready ? State.Done : State.Waiting);当!ready时状态机自环于WaitingOnEntry反复执行以刷新等待计时一旦ready则迁移到Done。状态变更通知事件Stateless 支持两类状态机事件状态转换State transition——每次状态变更时触发状态机转换完成State machine transition completed——在触发处理的最后、最后一个进入动作执行完毕之后触发。同步与异步注册方式// 状态转换同步 / 异步 stateMachine.OnTransitioned((transition) { }); stateMachine.OnTransitionedAsync((transition) { return Task.FromResult(0); }); // 转换完成同步 / 异步 stateMachine.OnTransitionCompleted((transition) { }); stateMachine.OnTransitionCompletedAsync((transition) { return Task.FromResult(0); });两类事件都携带Transition参数含Source、Destination、Trigger、Parameters。从源码的事件时序看src/Stateless/StateMachine.cs#L495-L514先执行源状态树的 Exit 动作 → 更新State→ 触发OnTransitioned→ 执行目标状态树的 Entry 动作含初始子状态递归→ 最后触发OnTransitionCompleted。事件实现位于 src/Stateless/OnTransitionedEvent.cs注册/注销 API 集中在 src/Stateless/StateMachine.cs#L802-L859。注销回调的三种方式除了注册Stateless 还提供三种取消订阅的途径状态转换注销同步/异步、转换完成注销同步/异步、以及一次性注销全部同步与异步一并清除。状态转换注销同步——先保存回调引用再交给OnTransitionedUnregisterAction transitionCallbackAction (transition) { }; stateMachine.OnTransitionedUnregister(transitionCallbackAction);状态转换注销异步FuncTransition, Task transitionAsyncCallback (transition) { return Task.FromResult(0); }; stateMachine.OnTransitionedAsyncUnregister(transitionAsyncCallback);转换完成注销同步Action transitionCompletedCallbackAction (transition) { }; stateMachine.OnTransitionCompletedUnregister(transitionCompletedCallbackAction);转换完成注销异步FuncTransition, Task transitionCompletedAsyncCallback (transition) { return Task.FromResult(0); }; stateMachine.OnTransitionCompletedAsyncUnregister(transitionCompletedAsyncCallback);注销全部回调同步与异步stateMachine.UnregisterAllCallbacks();UnregisterAllCallbacks()会同时清空已转换与转换完成两个事件上的所有同步、异步回调src/Stateless/StateMachine.cs#L855-L859。订阅/注销的实际使用可以参考 example/OnOffExample/Program.cs运行中按r订阅、按u注销OnTransitioned回调这正是长驻应用中防止内存泄漏的典型姿势。导出 DOT 状态图运行时可视化状态机非常有用代码是权威来源authoritative source状态图只是始终与代码保持同步的副产品。导出方式phoneCall.Configure(State.OffHook) .PermitIf(Trigger.CallDialled, State.Ringing, IsValidNumber); string graph UmlDotGraph.Format(phoneCall.GetInfo());UmlDotGraph.Format()src/Stateless/Graph/UmlDotGraph.cs接收GetInfo()返回的StateMachineInfo返回 DOT 图语言Graphviz 的 DOT 格式字符串例如digraph { OffHook - Ringing [labelCallDialled [IsValidNumber]]; }从 src/Stateless/Graph/UmlDotGraphStyle.cs 的实现看生成的图带有compoundtrue、node [shapeMrecord]、rankdirLR从左到右布局等预设并把进入/退出动作写进节点标签、守卫写进转换标签[守卫描述]、PermitDynamic渲染为菱形决策节点。底层先由 StateGraph 把StateMachineInfo转成符号化的图结构状态、转换、决策节点再由样式类GraphStyleBase派生类负责格式化输出。生成后的 DOT 文本可由支持 DOT 语言的各种工具渲染例如 Graphviz 的dot命令行工具或 viz.js 等浏览器端引擎。命令行示例生成 PDFdot -T pdf -o phoneCall.pdf phoneCall.dot如果你已有.dot源文件也可用dot -Tpng、-Tsvg等参数输出其他格式。完整可运行的 DOT 导出参考 example/TelephoneCallExample/PhoneCall.cs 中的ToDotGraph()方法。导出 Mermaid 状态图除了 DOTStateless 还能直接生成 Mermaid 图适用于 GitHub Markdown、Obsidian 等支持 Mermaid 的渲染引擎phoneCall.Configure(State.OffHook) .PermitIf(Trigger.CallDialled, State.Ringing); string graph MermaidGraph.Format(phoneCall.GetInfo());MermaidGraph.Format()src/Stateless/Graph/MermaidGraph.cs返回 MermaidstateDiagram-v2格式文本例如stateDiagram-v2 [*] -- OffHook OffHook -- Ringing : CallDialled这段文本可以直接嵌入支持 Mermaid 的 Markdown 文档渲染为图形Mermaid 导出还支持方向控制Format(machineInfo, MermaidGraphDirection? direction)可传入方向枚举src/Stateless/Graph/MermaidGraphDirection.cs包括LeftToRightLR、RightToLeftRL、TopToBottomTB、BottomToTopBT对应 MermaidGraphStyle 的GetDirectionCode映射。此外Mermaid 样式类会自动把含空格、冒号、连字符等非法字符的状态名净化为别名并在图中以别名 : 原名形式标注见BuildSanitizedNamedStateMap保证生成的图合法可渲染。异步触发器Async Triggers在提供TaskT的平台上StateMachine支持异步的进入/退出动作等stateMachine.Configure(State.Assigned) .OnEntryAsync(async () await SendEmailToAssignee());异步处理器必须使用带*Async()后缀的方法注册如OnEntryAsync、OnExitAsync、PermitIfAsync、PermitReentryIfAsync、OnTransitionedAsync、OnTransitionCompletedAsync等见 src/Stateless/StateMachine.Async.cs 及 src/Stateless/StateConfiguration.Async.cs。触发会调用异步动作的触发也必须用FireAsync()await stateMachine.FireAsync(Trigger.Assigned);异步触发同样支持 Queued 模式InternalFireQueuedAsyncsrc/Stateless/StateMachine.Async.cs#L177-L201事件与动作通过ConfigureAwait(RetainSynchronizationContext)决定是否回到调用方同步上下文。重要限制文档明示尽管StateMachine可以异步使用但它仍然是单线程的不能被多个线程并发使用。异步只表示动作可等待不代表线程安全。高级特性保留 SynchronizationContext在某些特定场景下必须让所有处理器方法都在消费方的SynchronizationContext上被调用。此时在创建状态机时设置RetainSynchronizationContext属性var stateMachine new StateMachineState, Trigger(initialState) { RetainSynchronizationContext true };该属性默认值为falsesrc/Stateless/StateMachine.cs#L96。文档给出的典型场景是 Microsoft Orleans 的 GrainGrain 需要保留SynchronizationContext才能调用其他 Grain。开启后异步回调会通过TaskScheduler.FromCurrentSynchronizationContext()调度src/Stateless/StateMachine.Async.cs#L313-L315。构建与运行环境Stateless 通过 src/Stateless/Stateless.csproj 多目标编译netstandard2.0;net462;net8.0;net9.0;net10.0因此可在 .NET runtime 4.0 及以上、几乎所有的现代 .NET 平台上运行对应文档中的 .NET Framework 4.6.2、.NET Standard 2.0、.NET 8.0/9.0/10.0。构建解决方案需要 Visual Studio 2017 或更高版本仓库根目录的 Stateless.sln 与测试解决方案 test/Stateless.Tests/Stateless.Tests.sln 可用于还原与编译。深入源码触发分发与执行链路结合文档内容理解Fire的底层链路有助于排查为什么没按预期迁移的问题。核心流程在InternalFireOnesrc/Stateless/StateMachine.cs#L392-L467参数校验若触发已配置参数_triggerConfiguration命中先ValidateParameters(args)查找处理程序TryFindHandler沿当前状态及其父状态链查找匹配的TriggerBehaviourResult未找到时执行_unhandledTriggerAction默认抛异常按行为类型分发IgnoredTriggerBehaviour→ 直接返回忽略ReentryTriggerBehaviour→ 执行退出/进入动作的自环处理HandleReentryTriggerDynamicTriggerBehaviour→ 动态求值目标后处理TransitioningTriggerBehaviour→ 常规转换若目标与源相同则不执行防止超状态意外重入InternalTriggerBehaviour→ 仅执行内部动作不改变状态。Queued 模式下InternalFireQueuedsrc/Stateless/StateMachine.cs#L358-L384用_eventQueue按 FIFO 排队正在处理时新触发的触发会入队等待从而保证 run-to-completion。示例项目与测试佐证仓库 example 目录提供了可直接运行的参考实现example/TelephoneCallExample文档首页的完整电话通话状态机含子状态、内部转换、参数化触发、事件订阅与 DOT 导出example/OnOffExample最简单的开关机示例——状态用string、触发用char演示通用类型支持与事件的订阅/注销example/JsonExample会员状态机与 JSON 序列化往返演示状态作为数据持久化example/AlarmExample用定时器实现临时状态布防延时、触发延时、超时返回演示CanFire、OnTransitioned与InvalidOperationException处理。测试方面test/Stateless.Tests 覆盖了本文涉及的大部分特性包括层次化状态与初始转换InitialTransitionFixture.cs、守卫与未处理触发StateMachineFixture.cs、动态触发DynamicTriggerBehaviourFixture.cs 等、内省信息GetInfoFixture.cs、异步触发AsyncTransitionTests.cs、AsyncFiringModesFixture.cs、DOT 与 Mermaid 导出DotGraphFixture.cs、MermaidGraphFixture.cs以及激活/停用ActiveStatesFixture.cs。需要了解某个 API 的确切语义时这些测试是最可靠的参照。设计目标与命名由来文档在结尾明确该项目目标本页面是 Stateless 近乎完整的描述且刻意保持最小化remain minimal——API 小而聚焦不引入额外运行时依赖。关于命名Stateless无状态的含义是——它实现的是关于状态转换的那套规则但当使用委托版本外部状态存储的构造函数时它自身并不维护任何内部状态。这也解释了为什么它如此适合嵌入 ORM 实体、可绑定属性与序列化模型状态机只是规则引擎状态存放位置完全由你决定。赞分享后端工作流自动化【免费下载链接】statelessA simple library for creating state machines in C# code项目地址https://gitcode.com/gh_mirrors/st/stateless点击查看免费下载相关推荐Stateless轻量级状态机与工作流引擎Stateless轻量级状态机与工作流引擎 项目介绍 Stateless 是一个用于在 .NET 代码中直接创建状态机和基于状态机的工作流的轻量级库。它允许开后端工作流自动化Stateless完全指南在C中构建轻量级状态机的终极教程Stateless完全指南在C 中构建轻量级状态机的终极教程 Stateless是一个简单而强大的C 状态机库专门为开发者提供轻量级、易用的状态机实现方案。后端工作流自动化【亲测免费】 状态机轻量级解决方案Stateless 使用指南状态机轻量级解决方案Stateless 使用指南 项目介绍 Stateless 是一个简单易用的库专为 C 语言设计旨在简化状态机在 .NET 项目中的创后端工作流自动化上一篇最精简TensorFlow入门从0到1掌握神经网络训练核心下一篇DC/OS 开源项目推荐创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑