07

26

状態マシンとタイムアウトについての考察

2011.07.26(20:58)

モデル駆動開発ツールでは、状態マシンはクラスとして実装され、イベントとタイムアウトに応答するクラスとして定義されています。今回は、状態マシンでのタイムアウトの扱いについて考察してみます。

timer10.jpg

まず最初の状態マシンです。2つの状態があり、off状態からon状態にはイベントevStartで遷移します。on状態からoff状態へはタイムアウトで遷移するようになっています。on状態には入状時振る舞いと、退状時振る舞いが定義されていて、それぞれprintfして呼ばれたことがわかるようになっています。

tm()はタイムアウトを表します。on状態に入状したところからタイマーが動き出し、5000msec、つまり5秒たったら、タイムアウトがおきて、on状態から退状してくる、という定義になっています。

この状態マシンでわかることは、off状態のときにはタイムアウトはおきない。on状態のときにはevStartというイベントには反応しない、ということです。状態マシンでは、このように、書いていないイベントやタイムアウトでは何もおきないというところをレビューすることが大切です。書いていないところには人間は気がつきにくいということから、状態マシン図よりも、状態遷移表のほうがいい、と言っている人もいます。

また、入状時振る舞いは、offからonに遷移するトリガー(evStartイベント)の作用として定義する、退状時振る舞いは、onからoffに遷移するトリガー(タイムアウト)の作用として定義する、方法もあります。これは設計判断であり、どちらがいい悪いというものではありません。たくさんの状態からon状態に遷移する場合や、作用と状態の凝集度結合度から考えて、on状態の入状時振る舞い、退状時振る舞いで処理したほうが間違いがない、という設計もあり、今回はその場合とします。

話を先にすすめます。

timer11.jpg

たとえば、お店のスタンプカードがあって、最後に利用した日から1年間有効、1年利用せずにいたら無効になる、みたいなモデルを考えます。無効な状態のときに一度でも利用すると有効な状態になり同時に1年タイマーがスタートし、有効な状態の間に利用があれば1年タイマーが再スタートします。

この状態マシンでは、on状態の間に、evStartイベントが起きたら、そこから再度 5000msec 数え直す、ということを表しています。on状態のときに evStartイベントを受け取ると、on状態をいったん退状して、もう一度 on状態に入状してきます。そのため、タイマーが再度スタートするのです。

このとき、on状態をいったん退状して、もう一度 on状態に入状してくるため、退状時振る舞いと、入状時振る舞いが1回ずつ実行されます。

退状時振る舞いと、入状時振る舞いが実行されてもかまわないのであれば、この状態マシンでOKです。もし、実行されたくなければ、別の設計が必要になります。要は、on状態から退状しなければいいのです。

timer12.jpg

これが、その解法のひとつです。(ほかにもあるかもしれません)入状時振る舞いと退状時振る舞いには手を加えないで、on状態に入状したときに一度、on状態から退状するときに一度だけ、実行するために、on状態の内部にサブ状態遷移を作って、そこで、evStartイベントとタイムアウトを処理します。こうすることで、ほんとうにon状態から退状するときまで、退状時振る舞いは呼ばれません。

さらに、問題を難しくします。これまでの状態マシンでは、evStart を受け取るたびに、タイムアウトが延長されますが、タイムアウトを計測しはじめるのは、最後にevStartを受け取ったタイミングです。スタンプカードでいうと、最後に利用したときから1年有効です、みたいな考え方です。

そうではなくて、会員になるときに入会金を払い、1年有効で、その会員の間に、会員延長の料金を払ってくれれば、もう1年有効ですよ、もし有効期間を過ぎれば会員でなくなりますよ、という場合を考えます。つまり、会員延長はいつやってもかまわないが、タイムアウトは入会した時から1年後、2年後といった、1年の倍数のところでしか起きない、という場合です。

この場合は、evStartイベントが発生したときに、タイマーを再スタートしてはなりません。タイマーは動かしたまま、延長処理をする、ということです。

それを解決しようとしたのが次の状態マシンです。

timer14.jpg

m_touch という bool の変数を用意します。on状態に移行したときに、m_touch を false に設定しておきます。on状態にいる間に、evStartを受け取ったら、m_touch を trueに変えておきます。そしてタイムアウトが起きたときに判断して、もし、m_touch が true なら false にして、internal 状態にとどまる。そうでなければ、off状態 に移行する。

このようにすると、1回延長のメカニズムはうまくいきそうです。が、タイムアウト時間の制御がうまくいきません。evStart イベントがおきたときに、internal 状態を退状してしまうので、タイマーが再スタートしてしまうのです。

これを解決するには、evStartイベントの処理と、タイムアウトの処理を別々にするしかありません。それを実現したのが次の状態マシンです。

timer15.jpg

on状態を2つの直交状態に分けました。直交状態はUML2.0で取り入れられた記法で、並行して成り立つ2つ以上の状態を表現するためのものです。2つの直交状態の片方では、evStart の発生をとらえ、もう片方ではタイムアウトの処理をおこないます。

このように 直交状態を用いて evStart が発生したことをラッチしておき、のちほどそれを利用するようなしくみを、ラッチステートパターンといいます。[1]

直交状態に分けることで、タイマーは他のイベントの発生タイミングに依存することなく、on状態に遷移してから決められたタイミングでタイムアウトをおこします。

このように、タイムアウトを含む状態マシンでは、タイムアウトの動きについて特別な見方をしてレビューする必要があります。

今回のまとめ:
■ 状態から退状すると、退状時振る舞いが必ず呼ばれてしまう。
■ 状態から退状すると、タイマーは停止する。
■ 状態から退状して入状すると、退状時振る舞いと入状時振る舞いの両方が連続して呼ばれてしまう。
■ 状態から退状して入状すると、タイマーは再スタートする。

参考文献
[1] ブルース・ダグラス リアルタイムUMLワークショップ p.197
http://www.amazon.co.jp/dp/4798121118/
[2] UML state machine
http://en.wikipedia.org/wiki/UML_state_machine

コメントの投稿

非公開コメント

プロフィール

島敏博

Shima Toshihiro 島敏博
信州アルプスハイランド在住。HaskellとElixirが好き。組み込みソフトウェアアーキテクト、C++プログラマ、山歩き、美術館巡り、和食食べ歩き、日本赤十字社救急法指導員、インデックス投資、クラシック音楽、SESSAME会員、状態マシン設計、モデル駆動開発、ソフトウェアプロダクトライン、Rubyist、実践ビジネス英語

■ ツイッター
http://twitter.com/saltheads
■ Facebook
http://www.facebook.com/saltheads
■ Qiita
http://qiita.com/saltheads

印刷する場合は、ブラウザの印刷メニューではなく、このページの上から3cmくらいの青いところにある、「印刷」を押してみてください。少しうまく印刷できます。まだ完全ではないのですが、これで勘弁してください。


カテゴリ
最新記事
月別アーカイブ
最新コメント
検索フォーム
リンク
sessame
RSSリンクの表示