
Queued Message Handler (QMH) や DQMH では ヘルパーループ で周期的にエンキューをすることで周期処理を実現できます。周期処理の数が固定で少なければヘルパーループを作るのもいいですが、数が多くなると毎回作るのも面倒になります。また、システム実行中に周期処理の数が動的に変化する場合は、静的なWhileループを追加していく方式では対応できません(最大数が分かっているなら最大数だけ用意すれば一応対応できる)。今回は、QMHでヘルパーループの数が不定の場合にどうするかを考えました。
ヘルパーループの作成
今回は、ヘルパーループとして次のVIを作成しました。

そんなに複雑なものではない…と思います。真ん中のケースストラクチャのケースも3つだけです。エラー処理が若干カオスな感じになっているかもしれません。このVIでは次のことを行います。
- 外で作成したり設定したりしたHelper Loop用のキューと周期処理に必要な情報(エンキュー先のMHLメッセージキュー、周期的に送るメッセージとデータ、周期)を入力します。
- Helper Loop用キューのデキューを待機します。タイムアウトしたら、Timeout処理でMHLに設定したメッセージとデータを送ります。
- 外部でHelper Loop用キューに周期変更のエンキューがされたら周期を変更します。
- 外部でHelper Loop用キューにExitがエンキューされたらこのVIを終了します。
エラーが発生した場合は、MHLのErrorケースにエラーをエンキューし、ヘルパーループを終了します。QMHのテンプレートにErrorのケースがあるのでErrorケースが存在することを前提としていますが、なければエラー時の送り先を変えたりFGVで扱ったりしてもいいかもしれません。そこは好みですね。
今回はこのヘルパーループをいくつ呼び出すか分からないので、VIプロパティで複数個呼び出してもいいような設定にします。具体的には、実行(Execution)のReentrancy(再入実行)を再入実行に変更します。ここはデフォルトはNon-reentrant execution (非再入実行)なので、ちゃんと変更します。

Shared clone(クローン共有)とPreallocated clone(クローン事前割り当て)については、どちらでもいいと思います。今回はPreallocated cloneにしていますが、これはActor FrameworkのTime-Delayed Send Message.viで呼び出しているTime-Delayed Send Message Core.viという類似のVIの設定がPreallocated cloneになっていたからそれに合わせただけです。動的に呼び出すのに事前割り当てってよく分からないですが、問題なく動いているので、実際はクローンが足りなくなったら追加でクローンを作っているのかもしれません。知らんけど。
ヘルパーループの取り扱い
今回はヘルパーループの数が決まっていない場合を想定しているので、ヘルパーループを動的に呼び出さないといけません。しかも、動的に呼び出したヘルパーループは基本は非同期で動きながら、呼び出し元のMHLで終了の制御ができないといけません。また、複数のヘルパーループを再入可能の形式で呼び出す必要があります。今回は次のようなlvlibを作りました。

このライブラリで外部から呼び出すのは次の3つです。
- Activate Helper Loop.vi
- Change Period of Helper Loop.vi
- Close Helper Loop.vi
SubにあるVIやctlは基本的にライブラリ内で使っているものです。
Activate Helper Loop.vi
Activate Helper Loop.viはヘルパーループを呼び出し、周期的にメッセージのエンキューを開始するVIです。

ここでHelper Loop用のキューを作成し、Helper Loop Core.viを起動しています。Helper Loop Core.viを呼び出す際にオプション”xC0″を指定していますが、これによって再入実行VIを再入実行として呼び出して非同期的に動作させることができます。その後、Helper Loop Core.viを非同期的に実行開始し、アクセススコープがPrivateのグローバル変数にHelper Loopキューをキーに、VIのリファレンスを値とする Map に登録します。このMapはヘルパーループをCloseするときにVIもCloseするために使います。ここで作成したHelper Loopキューを出力していますが、これは、MHLではこのキューを用いてヘルパーループを停止させたりするために使います。MHL側でDataクラスタに入れて使うことを想定しています。
Change Period of Helper Loop.vi
Change Period of Helper Loop.viでは、実行中のヘルパーループの周期を変更できます。

このVIはHelper Loop用キューに周期変更のメッセージと変更後の周期を送るだけの比較的シンプルなものです。周期に入力制限をかけていないので負の数だったり0だったり長すぎたりしてもそのままの値で周期を変更します。キューを使っているので、負の数の場合はClose Helper Loop.viでExitを送れば止まりますが、0の場合はエンキューのし過ぎになるかもしれません。
Close Helper Loop.vi
周期処理を終了する場合はClose Helper Loop.viを呼び出します。

ここでは、Exitメッセージをエンキューしています。Exitを送ると、ヘルパーループは停止処理を行い、その中でヘルパーループ用キューを破棄するので、ヘルパーループ用キューのステータスを見ることでヘルパーループが終了したか確認しています。厳密にはキュー破棄のタイミング=VI終了ではないですが、まあ気にするほどのことではないでしょう。その後、ヘルパーループ用キューのキーをMapから削除し、ヘルパーループ用キューに対応するHelper Loop Core.viのクローンのリファレンスを閉じます。
使用例
今回は、ここで作ったヘルパーループのライブラリを使って次のVIを作成しました。

Start/Stopボタンを押すと、LEDがPeriod [ms]配列の周期で点滅するだけというそんなに凝ったものではないです。ブロックダイアグラムはこんな感じです。

典型的なQMHですね。ここで見えるMHLのStartケースでヘルパーループを呼び出しています。ここではPeriod [ms]の要素数だけヘルパーループを作り、各ヘルパーループで”Update LED”メッセージと指標データをPeriodの周期でエンキューするように設定しています。それと、ここで作ったHelper Loop Queueを今回は配列としてMHLのデータクラスタに入れています。
Update LEDケースでは次の処理をしています。

ヘルパーループが独立した周期でエンキューし、指標で指定したLED配列要素が反転するので、LEDがそれぞれの周期で独立的に点滅します。
周期の変更はこんな感じです。

指定した指標のヘルパーループに周期を送っています。これだけだと何で変更したヘルパーループにだけ周期を送っているか分からないですが、それはイベント処理ループでうまくやっています。…まあ、Old ValueとNew Valueで異なる指標を取り出してMHLに送っているだけですが。
ヘルパーループの終了処理はこんな感じです。

これは使っていたすべてのHelper Loop Queueに対してClose Helper Loop.viを実行しているだけですね。これによりこれまで使っていたヘルパーループ用のキューは全部不要になるので、MHLのHelper Loop Queues配列を空配列にしています。
VI自体の終了処理はこのようにしています。

ここではHelper Loop Queues配列にあるすべてのヘルパーループを止めています。空配列なら何もしません。なので、ヘルパーループが動いているときでも動いているヘルパーループがなくても問題なくヘルパーループを止めて終了する…はずです。
この例ではLEDの数が8個で固定でしたが、この数を増やしたい場合でもVIを大幅に変更せずに対応できます。これが動的呼び出しのメリットですね。…まあ、ちゃんと動作することを確認するのが大変でしたが。
まとめ
ここでは、ヘルパーループの数が大量にあったり不定であったりしても効率的に対応する方法を紹介しました。まあ、大量といっても限度がありますが。ここで作ったHelper Loop.lvlibをGithubに置いておきます。

ここでは、例えばメッセージをUTF-8で扱いたい場合や、周期処理中に送るデータを変えたりしたい場合などの対応はしていないので、必要なら改造していただければと思います。あと、1回作ったヘルパーループを毎回閉じるのではなく最後まで使いまわしたい場合は、ヘルパーループの状態管理が大変になると思うのでがんばってください。私も最初状態管理しようとしましたが、面倒そうだったので早々にあきらめました。ソフトウェアタイミングでやるような周期処理ならそこまでがんばらなくてもいいんじゃないかな?知らんけど。
