最近在折騰一些控制相關的軟件設計,想起來狀態機這個東西,對解決一些控制系統狀態切換仍是挺有用的。git
狀態機(有限狀態自動機)網上有不少介紹。簡單理解就是定義一系列狀態,經過一系列的事件,可使得狀態能夠相互之間切換。github
若是不使用狀態機的思想來編程,那麼針對過程的編程方法會使得程序拓展性變差,而且不容易調試。而狀態機只須要定義好了各類狀態和狀態切換之間的事件,你只管觸發事件,剩下的事情它本身就自動完成了(畢竟名稱叫作有限狀態自動機),這對於不少須要定義各類控制階段的系統簡直是完美適配。瞭解到.NET也有不少庫能夠實現這些功能,本文主要介紹一下Stateless的應用。shell
Stateless能夠建立極簡的狀態機與對應的工做流。不少項目(包括VisualStudio Extension、AIlab)都有使用到它。編程
它支持如下特性:c#
也有幾點須要注意的:安全
安裝起來很簡單,直接在nuget中安裝便可:多線程
Install-Package Stateless
用起來也挺簡單的,以打電話這個事情爲例,針對打電話的種種動做和狀態作成一個狀態機。
須要先定義一些狀態和事件/觸發器,電話有撥號、接通、留言等事件,有響鈴、掛起、掛斷等事件:app
//代碼來自官方示例,能夠在官方github庫上找到,略有修改以完整展現功能。 enum Trigger { CallDialed, CallConnected, LeftMessage, PlacedOnHold, TakenOffHold, PhoneHurledAgainstWall, MuteMicrophone, UnmuteMicrophone, SetVolume } enum State { OffHook, Ringing, Connected, OnHold, PhoneDestroyed }
而後就是建立一個狀態機了:less
_machine = new StateMachine<State, Trigger>(() => _state, s => _state = s);
最後也是最須要詳細解釋的,就是配置狀態機的行爲了:異步
/* 爲了解釋儘量多的功能,如下程序修改了官方的代碼,能夠在官方找能夠直接執行的代碼。 */ //使用Permit指示發生某個事件後,從一個狀態變換到另一個狀態。 _machine.Configure(State.OffHook) .Permit(Trigger.CallDialed, State.Ringing); //設置一個帶參數的事件,這個事件是CallDialed的類型 var _setCalleeTrigger = _machine.SetTriggerParameters<string>(Trigger.CallDialed); _machine.Configure(State.Ringing) //容許從新進入當前的狀態,這個過程會觸發進入和退出動做 .PermitReentry(Trigger.Ringing) //使用OnEntryFrom指示在觸發這個狀態的時候,運行某個動做,這裏指定的是一個帶參數的事件 .OnEntryFrom(_setCalleeTrigger, callee => OnDialed(callee), "Caller number to call") .Permit(Trigger.CallConnected, State.Connected); _machine.Configure(State.OnHold) //定義子狀態 .SubstateOf(State.Connected) .Permit(Trigger.TakenOffHold, State.Connected) .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed); _machine.Configure(State.Connected) //進入狀態的時候執行動做 .OnEntry(t => StartCallTimer()) //離開狀態執行動做 .OnExit(t => StopCallTimer()) //狀態不變化,可是響應某種事件,和PermitReentry不一樣,它不會觸發進入和退出的動做 .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) //指定在發生同一種事件的時候,根據事件的參數不一樣而決定進入不一樣的狀態。 .PermitIf(_setCalleeTrigger, State.Connected, callee => string.IsNullOrWhiteSpace(callee)) .PermitIf(_setCalleeTrigger, State.Connected, callee => !string.IsNullOrWhiteSpace(callee)) //若是沒有定義這個事件而發生了這個事件,會彈出異常。經過指定忽略某一類事件,能夠避免這個狀況。 .Ignore(Trigger.CallDialled); //固然也可使用這個來避免彈出上面說的異常 _machine.OnUnhandledTrigger((state, trigger) => { }); //可使用異步調用,可是必需要在觸發事件的時候,使用FireAsync _machine.Configure(State.PhoneDestroyed) .OnEntryAsync(async () => await SendEmailToAssignee());
配置好了各狀態之間的轉換,下面就是觸發事件了。
public void Dialed(string callee) { //有參數的觸發 _machine.Fire(_setCalleeTrigger, callee); } public void Connected() { //無參數的觸發 _machine.Fire(Trigger.CallConnected); } public async Task PhoneDestroy() { //異步觸發 await _machine.FireAsync(Trigger.PhoneDestroyed); } public string ToDotGraph() { //導出DOT GRAPH return UmlDotGraph.Format(_machine.GetInfo()); }
外部調用很簡潔:
phoneCall.Dialed("Prameela"); phoneCall.Connected(); phoneCall.SetVolume(2); phoneCall.Hold();
只須要調用事件便可,別的都會按照咱們設置好的動做來進行了,很是自動化。
Stateless能夠很好地實現狀態機,有點事件驅動的編程的感受,但本質上不一樣,Stateless核心是各個狀態的遷移。
雖然Stateless很小巧方便,可是還有有不少地方不盡如人意(官方說這就是他們本身的設計目標,維持極簡):
還有一個Appccelerate.StateMachine
(地址),這個支持四種不一樣的狀態機實現:
其中active的是線程安安全的。另外,它還支持狀態、事件的持久化,拓展性強。用法差很少,只是配置的關鍵字上面有一點區別,你們能夠自行翻文檔。