C#ステートマシンStateless

4821 ワード

最近、いくつかの制御関連のソフトウェア設計を振り回して、ステータスマシンというものを思い出して、いくつかの制御システムのステータス切替を解決するのに役立ちます.
ステータスマシン(有限ステータスオートマチック)のネット上では多くの紹介があります.簡単に理解すると、一連の状態を定義し、一連のイベントを通じて、状態を互いに切り替えることができる.
ステータスマシンのアイデアを使用してプログラミングしないと、プロセスに対するプログラミング方法がプログラムの拡張性を悪くし、デバッグが容易ではありません.ステータスマシンは、さまざまなステータスとステータス切り替えの間のイベントを定義するだけで、イベントをトリガーするだけで、残りのことは自分で自動的に完了します(結局、名前は有限ステータスオートマチックと呼ばれています).これは、さまざまな制御フェーズを定義する必要がある多くのシステムにとって完璧です..NETにもこれらの機能を実現できるライブラリがたくさんあることを知っていますが、ここでは主にStatelessの応用を紹介します.
Statelessの紹介
Statelessでは、非常にシンプルなステートマシンと対応するワークフローを作成できます.VisualStudio Extension、AIlabなど多くのプロジェクトで使用されています.
次の機能をサポートします.
  • は、状態およびトリガイベント
  • として様々なタイプをサポートする.
  • サポートステータス継承
  • は、ステータスイン/オフイベント
  • をサポートする.
  • は、条件状態遷移
  • をサポートする.
  • ステータス/転送クエリー
  • をサポート
    注意すべき点もいくつかあります.
  • 非同期構文をサポートしますが、単一スレッドであり、スレッドが安全ではありません.
  • は、DOT graph
  • を導出することができる
    インストールは簡単です.nugetに直接インストールすればいいです.
    Install-Package Stateless
    

    Stateless使用
    使うのも簡単ですが、電話をかけることを例にとると、電話をかけるいろいろな動作や状態に対してステータスマシンを作ります.まず、いくつかの状態とイベント/トリガを定義する必要があります.電話にはダイヤル、接続、伝言などのイベントがあり、ベルが鳴る、切る、切るなどのイベントがあります.
    //        ,     github    ,           。
    enum Trigger
    {
        CallDialed,
        CallConnected,
        LeftMessage,
        PlacedOnHold,
        TakenOffHold,
        PhoneHurledAgainstWall,
        MuteMicrophone,
        UnmuteMicrophone,
        SetVolume
    }
    
    enum State
    {
        OffHook,
        Ringing,
        Connected,
        OnHold,
        PhoneDestroyed
    }
    

    次にステートマシンを作成します.
    _machine = new StateMachine(() => _state, s => _state = s);
    

    最後に最も詳細な説明が必要なのは、ステータスマシンを構成する動作です.
    /*
               ,            ,               。
    */
    
    //  Permit         ,              。
    _machine.Configure(State.OffHook)
        .Permit(Trigger.CallDialed, State.Ringing);
    
    //          ,     CallDialed   
    var _setCalleeTrigger = _machine.SetTriggerParameters(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(_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はコンパクトで便利ですが、まだ多くの点で満足していません(公式には彼ら自身の設計目標であり、極めて簡単に維持されています):
  • は起動と停止の言い方がなく、一般的に構造関数の中で作成するのがずっと有効です.
  • はスレッドセキュリティの
  • ではありません.
  • 拡張性有限
  • もう1つのAppccelerate.StateMachine(アドレス)は、4つの異なるステータスマシン実装をサポートします.
  • Passive State Machine:同期シングルスレッド処理状態変換
  • Active State Machine:同期マルチスレッド処理状態変換
  • Async Passive State Machine:非同期単一スレッド処理状態変換
  • Async Active State Machine:非同期マルチスレッド処理状態変換
  • ここでactiveはスレッドが安全です.また、ステータス、イベントの永続化をサポートし、拡張性が高い.使い方の差は多くありませんが、設定したキーワードに少し違いがあるので、自分でドキュメントをめくることができます.