【C#】【Stateless】 Readme 翻译

原文地址:https://github.com/dotnet-state-machine/stateless

Stateless

直接在.NET代码中创建状态机和基于轻量级状态机的工作流:

var phoneCall = new StateMachine<State, 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())
    .InternalTransition<int>(_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);

这个项目,以及上面的例子,是受到简单状态机(存档)的启发。

特性

支持大多数标准状态机结构:

  • 对任何.NET类型(数字、字符串、枚举等)的状态和触发器的通用支持
  • Hierarchical states
  • 状态的进入/退出动作
  • 保护子句来支持条件转换
  • 内省

还提供了一些有用的扩展:

  • 能够在外部存储状态(例如,在由ORM跟踪的属性中)
  • 参数化的触发器
  • 可重入状态
  • 导出到DOT图

状态分层

在下面的例子中,OnHold状态是Connected状态的子状态。这意味着OnHold呼叫仍然处于连接状态。

phoneCall.Configure(State.OnHold)
    .SubstateOf(State.Connected)
    .Permit(Trigger.TakenOffHold, State.Connected)
    .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);

除了StateMacheine.State属性之外,IsInstate(State)也可以精确的报告当前状态。

IsInStaet(State)也会考虑子状态,所以如果上文中的例子处于OnHold状态,IsInState(State.Connected)的结果也会为true

进入/退出动作

在这个例子中,StartCallTimer()方法将在呼叫连接时执行。StopCallTimer()将在呼叫完成时执行。

呼叫可以在ConnectedOnHold状态之间移动,而不需要重复调用StartCallTimer()StopCallTimer()方法,因为OnHold状态是Connected状态的子状态。

进入/退出动作处理程序可以使用Transition类型的参数提供,该参数描述触发器、源和目标状态。

内部转换

有时需要处理触发器,但状态不应该改变。这是一种内部转变。使用InternalTransition

初始状态转换

子状态可以标记为初始状态。当状态机进入超级状态时,它也会自动进入子状态。可以这样配置:

    sm.Configure(State.B)
        .InitialTransition(State.C);

    sm.Configure(State.C)
        .SubstateOf(State.B);

由于Stateless的内部结构,它不知道何时启动。这使得无法以传统方式处理初始转换。可以通过添加一个虚拟初始状态来绕过这个限制,然后使用Activate()启动状态机。

    sm.Configure(InitialState)
        .OnActivate(() => sm.Fire(LetsGo))
        .Permit(LetsGo, StateA)

外部状态存储器

Stateless被设计为嵌入到各种应用程序模型中。例如,一些ORM对可能存储映射数据的位置提出了要求,UI框架通常要求将状态存储在特殊的可绑定属性中。为此,StateMachine构造函数可以接受用于读写状态值的函数参数:

var stateMachine = new StateMachine<State, Trigger>(
    () => myState.Value,
    s => myState.Value = s);

在本例中,状态机将使用myState对象进行状态存储。

另一个示例可以在位于example文件夹中的JsonExample解决方案中找到。

激活/取消激活

在存储对象状态之前可能需要执行一些代码,在恢复对象状态时也是如此。使用DeactivateActivate。激活应该只在正常操作开始之前调用一次,在状态存储之前调用一次。

自省

状态机可以通过StateMachine.PermittedTriggers 属性提供可以在当前状态下成功触发的触发器列表。使用 StateMachine.GetInfo() 检索有关状态配置的信息。

保护子句

状态机将根据保护子句在多个转换之间进行选择,例如:

phoneCall.Configure(State.OffHook)
    .PermitIf(Trigger.CallDialled, State.Ringing, () => IsValidNumber)
    .PermitIf(Trigger.CallDialled, State.Beeping, () => !IsValidNumber);

在一个状态内的保护子句必须是互斥的(多个保护子句不能同时有效)。子状态可以通过重新指定它们来覆盖转换,但子状态不能禁止超状态允许的转换。

每当触发器被触发时,将评估保护子句。因此,应该使保护子句无副作用。

参数化的触发器

强类型参数可以分配给触发器:

var assignTrigger = stateMachine.SetTriggerParameters<string>(Trigger.Assign);

stateMachine.Configure(State.Assigned)
    .OnEntryFrom(assignTrigger, email => OnAssigned(email));

stateMachine.Fire(assignTrigger, "joe@example.com");

触发器参数可用于使用PermitDynamic()配置方法动态选择目标状态。

忽略转换和可重入状态

触发没有关联允许转换的触发器将导致抛出异常。

要忽略某些状态下的触发器,使用ignore (TTrigger)指令:

phoneCall.Configure(State.Connected)
    .Ignore(Trigger.CallDialled);

或者,一个状态可以被标记为可重入的,这样它的进入和退出动作即使转换是来源或目标是自身时也会触发:

stateMachine.Configure(State.Assigned)
    .PermitReentry(Trigger.Assigned)
    .OnEntry(() => SendEmailToAssignee());

默认情况下,必须显式忽略触发器。要覆盖Stateless在触发未处理触发器时抛出异常的默认行为,请使用OnUnhandledTrigger方法配置状态机:

stateMachine.OnUnhandledTrigger((state, trigger) => { });

状态更改通知(事件)

Stateless支持两种类型的状态机事件:

  • 状态转换
  • 状态转换完成
状态转换
stateMachine.OnTransitioned((transition) => { });

此事件将在每次状态机更改状态时调用。

状态机转换完成
stateMachine.OnTransitionCompleted((transition) => { });

在触发器处理的最后一步,在最后一个进入动作之后调用此事件。

导出到DOT图

在运行时可视化状态机是很有用的。使用这种方法,代码是权威的来源,状态图是总是最新的副产品。

phoneCall.Configure(State.OffHook)
    .PermitIf(Trigger.CallDialled, State.Ringing, IsValidNumber);
    
string graph = UmlDotGraph.Format(phoneCall.GetInfo());

UmlDotGraph.Format()方法以DOT图形语言的形式返回状态机的字符串表示,例如:

digraph {
  OffHook -> Ringing [label="CallDialled [IsValidNumber]"];
}

然后可以通过支持DOT图形语言的工具来呈现,例如来自graphviz.orgviz.jsDOT命令行工具。请参阅http://www.webgraphviz.com获取即时满足。
命令行示例:dot -T pdf -o phoneCall.pdf phoneCall.dot以生成PDF文件。

异步触发

在提供Task<T>的平台上,StateMachine支持async进入/退出动作等等:

stateMachine.Configure(State.Assigned)
    .OnEntryAsync(async () => await SendEmailToAssignee());

在这些情况下,异步处理程序必须使用*Async()方法注册。

要触发调用异步动作的触发器,必须使用FireAsync()方法:

await stateMachine.FireAsync(Trigger.Assigned);

注意:虽然StateMachine可以被_异步的_使用,但它仍然是单线程的,不能被多个线程_同步的_使用。

其他功能

保留SynchronizationContext

在特定的情况下,所有的处理程序方法都必须用消费者的SynchronizationContext来调用,在创建时设置RetainSynchronizationContext属性:

var stateMachine = new StateMachine<State, Trigger>(initialState)
{
    RetainSynchronizationContext = true
};

例如,在Microsoft Orleans Grain中设置这个是至关重要的,它需要SynchronizationContext来调用其他Grain。

生成

Stateless 可以在 .NET Framework 4.6.2、.NET Standard 2.0 和 .NET 8.0,在 .NET 运行时版本 4+ 和几乎所有现代 .NET 平台上运行。需要 Visual Studio 2017 或更高版本才能生成解决方案

贡献

我们欢迎对这个项目的贡献。查看CONTRIBUTING.md了解更多信息。

项目目标

本页几乎是对Stateless的完整描述,其明确的目标是保持最小化。

如果您想报告问题或讨论功能,请使用问题跟踪器或讨论页面。

(为什么取这个名字?Stateless实现了一组关于状态转换的规则,但是,至少在使用构造函数的委托版本时,它本身不维护任何内部状态。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值