淺析C# 狀態(tài)機(jī)Stateless
最近在折騰一些控制相關(guān)的軟件設(shè)計(jì),想起來狀態(tài)機(jī)這個(gè)東西,對(duì)解決一些控制系統(tǒng)狀態(tài)切換還是挺有用的。
狀態(tài)機(jī)(有限狀態(tài)自動(dòng)機(jī))網(wǎng)上有很多介紹。簡(jiǎn)單理解就是定義一系列狀態(tài),通過一系列的事件,可以使得狀態(tài)可以相互之間切換。
如果不使用狀態(tài)機(jī)的思想來編程,那么針對(duì)過程的編程方法會(huì)使得程序拓展性變差,并且不容易調(diào)試。而狀態(tài)機(jī)只需要定義好了各種狀態(tài)和狀態(tài)切換之間的事件,你只管觸發(fā)事件,剩下的事情它自己就自動(dòng)完成了(畢竟名稱叫做有限狀態(tài)自動(dòng)機(jī)),這對(duì)于很多需要定義各種控制階段的系統(tǒng)簡(jiǎn)直是完美適配。了解到.NET也有很多庫(kù)可以實(shí)現(xiàn)這些功能,本文主要介紹一下Stateless的應(yīng)用。
Stateless介紹
Stateless可以創(chuàng)建極簡(jiǎn)的狀態(tài)機(jī)與對(duì)應(yīng)的工作流。很多項(xiàng)目(包括VisualStudio Extension、AIlab)都有使用到它。
它支持以下特性:
- 支持各種類型作為狀態(tài)和觸發(fā)事件
- 支持狀態(tài)繼承
- 支持狀態(tài)進(jìn)入/離開事件
- 支持條件狀態(tài)轉(zhuǎn)移
- 支持狀態(tài)/轉(zhuǎn)移查詢
也有幾點(diǎn)需要注意的:
- 它支持異步語(yǔ)法,但是它是單線程的,不是線程安全的。
- 可以導(dǎo)出DOT graph
安裝起來很簡(jiǎn)單,直接在nuget中安裝即可:
Install-Package Stateless
Stateless使用
用起來也挺簡(jiǎn)單的,以打電話這個(gè)事情為例,針對(duì)打電話的種種動(dòng)作和狀態(tài)做成一個(gè)狀態(tài)機(jī)。
需要先定義一些狀態(tài)和事件/觸發(fā)器,電話有撥號(hào)、接通、留言等事件,有響鈴、掛起、掛斷等事件:
//代碼來自官方示例,可以在官方github庫(kù)上找到,略有修改以完整展示功能。
enum Trigger
{
CallDialed,
CallConnected,
LeftMessage,
PlacedOnHold,
TakenOffHold,
PhoneHurledAgainstWall,
MuteMicrophone,
UnmuteMicrophone,
SetVolume
}
enum State
{
OffHook,
Ringing,
Connected,
OnHold,
PhoneDestroyed
}
然后就是創(chuàng)建一個(gè)狀態(tài)機(jī)了:
_machine = new StateMachine<State, Trigger>(() => _state, s => _state = s);
最后也是最需要詳細(xì)解釋的,就是配置狀態(tài)機(jī)的行為了:
/*
為了解釋盡可能多的功能,以下程序修改了官方的代碼,可以在官方找可以直接執(zhí)行的代碼。
*/
//使用Permit指示發(fā)生某個(gè)事件后,從一個(gè)狀態(tài)變換到另外一個(gè)狀態(tài)。
_machine.Configure(State.OffHook)
.Permit(Trigger.CallDialed, State.Ringing);
//設(shè)置一個(gè)帶參數(shù)的事件,這個(gè)事件是CallDialed的類型
var _setCalleeTrigger = _machine.SetTriggerParameters<string>(Trigger.CallDialed);
_machine.Configure(State.Ringing)
//允許重新進(jìn)入當(dāng)前的狀態(tài),這個(gè)過程會(huì)觸發(fā)進(jìn)入和退出動(dòng)作
.PermitReentry(Trigger.Ringing)
//使用OnEntryFrom指示在觸發(fā)這個(gè)狀態(tài)的時(shí)候,運(yùn)行某個(gè)動(dòng)作,這里指定的是一個(gè)帶參數(shù)的事件
.OnEntryFrom(_setCalleeTrigger, callee => OnDialed(callee), "Caller number to call")
.Permit(Trigger.CallConnected, State.Connected);
_machine.Configure(State.OnHold)
//定義子狀態(tài)
.SubstateOf(State.Connected)
.Permit(Trigger.TakenOffHold, State.Connected)
.Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);
_machine.Configure(State.Connected)
//進(jìn)入狀態(tài)的時(shí)候執(zhí)行動(dòng)作
.OnEntry(t => StartCallTimer())
//離開狀態(tài)執(zhí)行動(dòng)作
.OnExit(t => StopCallTimer())
//狀態(tài)不變化,但是響應(yīng)某種事件,和PermitReentry不同,它不會(huì)觸發(fā)進(jìn)入和退出的動(dòng)作
.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)
//指定在發(fā)生同一種事件的時(shí)候,根據(jù)事件的參數(shù)不同而決定進(jìn)入不同的狀態(tài)。
.PermitIf(_setCalleeTrigger, State.Connected, callee => string.IsNullOrWhiteSpace(callee))
.PermitIf(_setCalleeTrigger, State.Connected, callee => !string.IsNullOrWhiteSpace(callee))
//如果沒有定義這個(gè)事件而發(fā)生了這個(gè)事件,會(huì)彈出異常。通過指定忽略某一類事件,可以避免這個(gè)情況。
.Ignore(Trigger.CallDialled);
//當(dāng)然也可以使用這個(gè)來避免彈出上面說的異常
_machine.OnUnhandledTrigger((state, trigger) => { });
//可以使用異步調(diào)用,但是必須要在觸發(fā)事件的時(shí)候,使用FireAsync
_machine.Configure(State.PhoneDestroyed)
.OnEntryAsync(async () => await SendEmailToAssignee());
配置好了各狀態(tài)之間的轉(zhuǎn)換,下面就是觸發(fā)事件了。
public void Dialed(string callee)
{
//有參數(shù)的觸發(fā)
_machine.Fire(_setCalleeTrigger, callee);
}
public void Connected()
{
//無參數(shù)的觸發(fā)
_machine.Fire(Trigger.CallConnected);
}
public async Task PhoneDestroy()
{
//異步觸發(fā)
await _machine.FireAsync(Trigger.PhoneDestroyed);
}
public string ToDotGraph()
{
//導(dǎo)出DOT GRAPH
return UmlDotGraph.Format(_machine.GetInfo());
}
外部調(diào)用很簡(jiǎn)潔:
phoneCall.Dialed("Prameela");
phoneCall.Connected();
phoneCall.SetVolume(2);
phoneCall.Hold();
只需要調(diào)用事件即可,別的都會(huì)按照我們?cè)O(shè)置好的動(dòng)作來進(jìn)行了,非常自動(dòng)化。
總結(jié)
Stateless可以很好地實(shí)現(xiàn)狀態(tài)機(jī),有點(diǎn)事件驅(qū)動(dòng)的編程的感覺,但本質(zhì)上不同,Stateless核心是各個(gè)狀態(tài)的遷移。
雖然Stateless很小巧方便,但是還有有很多地方不盡如人意(官方說這就是他們自己的設(shè)計(jì)目標(biāo),維持極簡(jiǎn)):
- 沒有啟動(dòng)和停止的說法,一般在構(gòu)造函數(shù)里面創(chuàng)建就一直有效。
- 不是線程安全的
- 拓展性有限
還有一個(gè)Appccelerate.StateMachine(地址),這個(gè)支持四種不同的狀態(tài)機(jī)實(shí)現(xiàn):
- Passive State Machine: 同步單線程處理狀態(tài)轉(zhuǎn)換
- Active State Machine: 同步多線程處理狀態(tài)轉(zhuǎn)換
- Async Passive State Machine: 異步單線程處理狀態(tài)轉(zhuǎn)換
- Async Active State Machine: 異步多線程處理狀態(tài)轉(zhuǎn)換
其中active的是線程安安全的。另外,它還支持狀態(tài)、事件的持久化,拓展性強(qiáng)。用法差不多,只是配置的關(guān)鍵字上面有一點(diǎn)區(qū)別,大家可以自行翻文檔。
以上就是淺析C# 狀態(tài)機(jī)Stateless的詳細(xì)內(nèi)容,更多關(guān)于C# 狀態(tài)機(jī)Stateless的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#中comboBox實(shí)現(xiàn)三級(jí)聯(lián)動(dòng)
給大家分享了C#中comboBox實(shí)現(xiàn)三級(jí)聯(lián)動(dòng)的全部代碼,代碼經(jīng)過測(cè)試,有興趣的朋友跟著做一下。2018-03-03
C#基于Spire.Doc for .NET從URL下載Word文檔的實(shí)現(xiàn)方案
在日常的軟件開發(fā)中,我們經(jīng)常會(huì)遇到這樣的場(chǎng)景:需要從一個(gè)指定的 URL 地址下載文件,當(dāng)涉及到 Word 文檔這類復(fù)雜的二進(jìn)制格式時(shí),情況就變得不那么簡(jiǎn)單了,本文將深入探討在 C# 應(yīng)用程序中,如何高效、可靠地從 URL 下載 Word 文檔,需要的朋友可以參考下2025-12-12
深入理解C# abstract和virtual關(guān)鍵字
深入理解C# abstract和virtual關(guān)鍵字,學(xué)習(xí)c#的朋友可以參考下。2011-06-06
C# SynchronizationContext以及Send和Post使用解讀
這篇文章主要介紹了C# SynchronizationContext以及Send和Post使用解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
C#讀取或設(shè)置ScrollLock狀態(tài)的方法
這篇文章主要介紹了C#讀取或設(shè)置ScrollLock狀態(tài)的方法,涉及C#操作ScrollLock滾動(dòng)狀態(tài)的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
winform 調(diào)用攝像頭掃碼識(shí)別二維碼的實(shí)現(xiàn)步驟
這篇文章主要介紹了winform 調(diào)用攝像頭掃碼識(shí)別二維碼的實(shí)現(xiàn)步驟,幫助大家更好的理解和學(xué)習(xí)使用winform,感興趣的朋友可以了解下2021-02-02

