Actor Framework

Queued Message Handler (QMH) やDQMHを利用していて、次のように思ったことはありますか?

  • トップからの指示と実働部隊の間に抽象化した層を入れたい。
  • メッセージングでタイプミスのリスクを減らしたい。

抽象化した層の例としては、HAL (Hardware Abstraction Layer) 的なものですね。トップから計測器の処理を呼び出す際に、トップは最下層で動いているものによらず計測の指示を出し、中間職が実際の作業者に作業を指示する形です。QMHやDQMHではWhileループやモジュールがクラスになっていないので、階層構造をとるようなものではありませんが、Actor Framework (AF)ならActorクラスをネストすることで階層構造を作ることができます。

メッセージングについては、QMHやDQMHでは文字列でやることが多いのでタイプミスのリスクがありますが、AFではクラスのオーバーライドとDynamic Dispatchという機能を利用してメッセージの送信をしています。これは、メッセージを処理するVI (Do.vi) に入力されたクラスの種類によって処理を変えるものです。メッセージ用のVIはツールで作れるため、タイプミスのリスクがなくなります。

…と前置きを記述しましたが、階層構造を作るのはかなり難しいです。私はやりたくありませんし、このやり方を紹介するほどAFを習熟しているわけではありません。それでもAFを触ってみたので、どう四苦八苦したか記述したいと思います。

LabVIEWにおけるActor Frameworkとは?

Actor Frameworkとは、アクターモデルに基づいた…というのは私は情報科学や数理科学の専門家ではないのでくわしくないです。その代わり、LabVIEWにおけるActor Frameworkについて紹介したいと思います。

LabVIEWにおけるActor Framework (AF)とは、複数のアクターが親子関係を持ちながらも並列に動作し、必要なときは連携をすることで、アプリケーションを実現するデザインパターン…というよりフレームワークです。アクターフレームワークとありますし。フレームワークなので、決まった手続きでアクターやアクションを作っていきます。特徴としては次の通りだと思います。

  • 各アクターは非同期で動く
  • アクター間の通信はキューで行う
  • アクター間の通信についてはAFのツールを使って行う
  • エラー処理もだいたい完成している(と思う)
  • QMHよりモジュールの結合度を下げることができる
  • アクター内あるいはアクター間で指示を送る際に文字列を使わない。代わりにメッセージクラスを使う
  • たいていのことはNIが用意しているActorクラスのメソッドをオーバーライドするか右クリックでメッセージクラスを作るかでやる
  • アクターは動的に呼び出されるのでデバッグが困難

詳細はNational Instruments公式のドキュメントを参照してください。

前提知識

AFを使うにあたって、知っておいたほうがいい前提知識は次の通りだと思います。

  • State Machineのデザインパターン
  • QMHのデザインパターン
  • LabVIEWオブジェクト指向プログラミング(LVOOP)。特にオーバーライドのしかた。

ユーザーイベントは必須ではありませんが、個人的に使いたいのでこれも知っておいたほうがいいと思います。

使い方

*ここでは、LabVIEW Community 2026 Q1を使用します。このバージョンには英語版しかありません。日本語版ではメニュー名などが異なることがあります。

作成するプロジェクトの説明

LabVIEWのスタートメニューからCreate Projectを選択すると、Actor Frameworkのテンプレートが存在します。しかし、個人的にはこのテンプレートは好みではないので、空のプロジェクトから開始します。あのテンプレートはアクターの起動方法に謎のクセがありますし。テンプレートを使用する場合、プロジェクトの条件無効シンボルに「AF_Debug_Trace」というシンボルがあり、これの値を「TRUE」にすることでデスクトップ実行トレースツールキットというアプリにイベントを通知できて、今どこの処理をしているか追跡できるようになりますが、なくてもいいでしょう。これでデバッグしたくなったら手動でこの条件無効シンボルを加えればいいだけですし。

今回は次のサンプルコードを作成します。

  • 操作画面でStart Acquisitionボタンを押したら、DAQアナログ入力で一定の周期でアナログ波形を取得する。
  • アナログ波形のサンプリングレートやサンプル数は固定。
  • 取得した波形のうち少なくとも1つが操作画面で設定したしきい値を超えたら集録を終了し、DAQデジタル入力でデジタル値を取得する。
  • Stop Acquisitionボタンを押したら正常に収録を終了する。DAQデジタル入力の値は取らない。
  • DAQは最初からつながっているものとし、DAQの情報は設定ファイルから取得する。

アプリケーションの全体像はこのようなイメージです

全体像
Launcher:Supervisorアクターを呼ぶ。
|-Supervisor:UIアクター、DAQ AIアクター、DAQ DIアクターを呼ぶ。エラー処理と全体統括。
     |-UI:操作画面。
     |-DAQ AI:DAQアナログ入力。
     |-DAQ DI:DAQデジタル入力。

Launcherが全体を統括し、各モジュールが非同期で動くイメージになります。本当はアナログ入力でしきい値を超えたらDAQデジタル出力でデジタル信号を変えて非常停止をするみたいな処理をするほうがリアリティがありますが、今回使用するDAQはシミュレーションデバイスで、シミュレーションデバイスに出力してもアプリケーションの見た目が変化するわけでもなく、実機につながないと変化がわからないので、今回はDAQデジタル入力にしました。

アクターの作成

アクターを作成するには、LabVIEWプロジェクトのターゲット(通常はマイコンピュータ)で右クリックをして、New→Actorを選択します。

crerate actor

これを選ぶと、アクター作成画面が表示されます。最初はアクターとなる「Supervisor」を作成します。Supervisorは親となるため、Inherit fromはActor(Actorのもととなるクラス)を選択します。

add actor

同様にUIアクター、DAQ AIアクター、DAQ DIアクターを作成します。これらのアクターはSupervisorから呼ばれる(ネストされる)わけですが、Supervisorアクターのプロパティやメソッドを継承するわけではないので、Inherit fromはActorのままにします。親アクター/子アクターという表現を使用していますが、クラスとしての親子関係はないのでややこしいですね。

image

アクターで使うVI

Actor Frameworkでは、Actorクラスですでに用意されているVIを必要に応じてオーバーライドして使用します。オーバーライドするには、作成されたlvclassで「右クリック→New→VI for Override..」を選択します。オーバーライドできるVIは次の通りです。

image

…正直なところ、知らないVIもあります。知っている範囲で各VIの役割を紹介します。

  • Actor Core.vi:これがアクターのメインです。QMHのメッセージ処理ループに相当するVIです。
  • Handle Error.vi:Actor Core.viのループでエラーが発生したときの処理をここに作ります。Actor Coreループの停止をエラー43で行っている関係で、停止する場合は必ずここを通ります。ここで行うエラー処理は復旧可能なものになります。
  • Pre Launch Init.vi:Actor Core.viの処理を開始する前に呼び出されるVIです。リファレンスを開いて使うもの(ユーザイベントなど)がある場合、ここで開くことができます。
  • Receive Message.vi:Actor Core.viでメッセージを受け取ったときの処理をします。基本的にはオーバーライドする必要はないと思います。
  • Stop Core.vi:Actor Core.viのループが終了した直後に呼び出されます。Actor Coreと並列にイベント処理ループを自作した場合などは、ここでそれを止める処理を作るといいです。
  • Uninit.vi:Actor Core.vi自体を終了した後に呼び出されます。リファレンスのクリアなどを行うといいと思います。
  • Handle Last Ack Core.vi:Uninitが終わった後のActorの最後の処理です。デフォルトでは呼び出したアクターにエラーを送ります。

この中でオーバーライドして使うのは、

Actor Core、Pre Launch Init、Stop Core、Uninit

ぐらいだと思います。正直なところ、Actor Coreだけオーバーライドしてもいいと思います。

UIアクターの作成

操作画面はUIアクターで作ります。ここでは次の処理を行います。

  • 波形集録を止めるしきい値を設定する。
  • Start Acquisitionボタンを押して、アナログ波形の集録を開始する。
  • 集録したアナログ波形を表示する。
  • Stop Acquisitionボタンを押して、波形の集録を止める。
  • 波形がしきい値を超えたら、デジタル入力値を表示する。
  • 画面を閉じたらアプリケーションを終了する。

アクターフレームワークでは、表示器を単純に配線して更新することができないので、今回はユーザーイベントを利用して表示器を更新します。今回オーバーライドするVIは次の通りです。

  • Actor Core:メインの処理を行う。
  • Pre Launch Init:ユーザーイベントの作成を行う。
  • Stop Core:Actor Coreに作ったイベント処理ループを止める。
  • Uninit:ユーザーイベントの破棄を行う。

エラー処理はHandle Last Ark Core.viに任せます。ここではまず、Actor Coreのフロントパネルに制御器を配置します。

Actor Core

これがActor Coreです。…これだけ見ても何をしているかわかりにくいですが、真ん中のサブVIに見えるもの(親クラスのメソッドを呼び出す関数)がQMHのメッセージ処理ループに相当するループ処理を行う部分です。この中を触る必要はありません。ここでは操作画面を作り、イベント処理ループを作ります。

UI Actor Core Final

これが画面です。あと、ブロックダイアグラムも実はこれが完成形です。ここからは、ここまで持っていくのにどのくらい苦労するかを紹介します。

プライベートデータの作成

LabVIEWクラスを用いる場合、クラスにctlファイルが生成されます。これはクラスに持たせるプロパティに相当するもので、ここにあるデータはクラスのメンバーしか参照できないものになっています。UIアクターのデータに何を入れるか?UIアクターでは表示器の更新をする必要がありますが、アクターフレームワークでは、Actor CoreがサブVI(正確には親メソッド)になっているため、QMHでのメッセージ処理ループに表示器を置くようなことができません。そのため、Actor Core内で処理した結果を表示器に反映するのに必要なデータをクラスのプライベートデータに入れる必要があります。NIのサンプルコードでは表示器のリファレンスをプライベートデータに入れていますが、個人的にそれは好きではないので、ユーザーイベントのリファレンスを入れることにします。

UI Class Property

今回は、Supervisorアクターからメッセージとデータが送られたら、これらのユーザーイベントを発生させ、UIアクターのイベント処理ループで表示器を更新します。また、ユーザーイベントの停止もユーザーイベントで行います。

Pre Launch Initの作成

Pre Launch Initは、Actor Coreの前に実行されます。なので、Actor Coreでループ処理に入る前の準備をします。実際のVIではPre Launch InitではなくActor Coreの最初に処理してもいいですが、今回はここでユーザーイベントの作成を行います。

UI Pre Launch Init

Pre Launch Initはこれで完成です。一番左にあるサブVIに見えるものは、Pre Launch Initの親メソッドを呼び出す関数で、この親メソッドでは何もしないので消しても構いません。あっても構いません。ユーザーイベントを作成するときは、型を指定するための定数にラベルをつけておく必要があります(正確にはラベル名を決める必要があり、表示は必須ではなかったと思います)。

Stop Coreの作成

Stop Coreは、Actor Coreのループ(イベント処理ループに相当するループ)が終了した直後に行う処理です。今回は、Actor Coreループと並列にイベント処理ループを実装しているので、イベント処理ループも止める必要があります。その処理をここで実装します。

image

ユーザーイベントStopの処理に関してはエラーが出ても無視していいと思うので、見ないようにしています。ここでもStop Coreの親メソッドを呼び出していますが、今回は子アクターを止める処理をしているので、残した方がいいです。

Uninitの作成

Uninitは、Actor Coreが完全に終了した後の処理になります。ここでは、使ったリファレンスを閉じる処理をするといいと思います。これもActor Coreの中でやってもいいですが、せっかくなので、ここで行うことにします。このVIでのエラーは重要ではないので無視するようにします。

UI Uninit

Actor Coreの作成

ここからが本番で、UIのメイン処理を作成します。UIの処理としては次のものがあります。

  • 画面を開き、初期化
  • Start Acquisition/Stop Acquisitionの指示
  • 画面の更新
  • 画面を閉じてアプリケーションの終了

アクターフレームワークでは、メッセージを受け取る側でメソッドを作ります。画面の初期化、画面の更新はSupervisorから指示を受け取って行うのでこれらはUI側でメッセージを作り、それ以外はUI→SupervisorのためSupervisor側でメッセージを作ります。まずは初期化のメソッドを作ります。メソッドを作るには、まずはそのためのVI(Initialize.vi)を作ります。

UI Initialize

このVIではStatusと波形の初期値をユーザーイベントで送ります。ただ、これだけではSupervisorから指示を受け取れないので、指示を受け取れるようにします。そうするには、作成したVIで右クリック→Actor Framework→Create Messageを選択します。

image

Create Messageを選ぶと、選んだVIをActor Coreで実行するVIとそのメッセージを送るVIが作成されます。

Actor Message

上記の場合は、Initializeのメッセージクラスが生成されます。

  • Initialize Msg.ctl:Initialize.viで作った入力端子(UIクラスとエラー以外)のデータを持ちます。今回の場合はDAQmx Physical ChannelとDAQmx Digital Channelです。
  • Send Initialize.vi:他のアクターからInitializeのDo.viを呼び出します。今回はSupervisorで使います。
  • Do.vi:Actor Coreが受け取ったメッセージがInitializeのものならInitializeを実行します。言い換えるとSend Initializeで呼ばれて実行します。

Do.viはすでに配置されている(正確にはDo.viのDynamic Dispatch VIが置かれていて、Actor Coreで受け取ったメッセージがInitialize MsgならオーバーライドされたInitializeのDo.viが呼ばれる)ので、Send Initialize.viをSupervisorに置けばInitializeの処理ができます。

同じように波形の更新とStatusを更新するメッセージを作ります。メッセージを作るたびにクラスやDo.viができるので結構面倒です。

今回はユーザーイベントで表示器を更新するので、更新した値をイベントストラクチャで受け取る処理を追加する必要があります。これはダイナミックイベントを登録すればできます。ダイナミックイベントといっても、ここでは動的にイベントが変わるわけではないです。

image

これはSupervisorからStatusを受け取ったときの処理です。SupervisorでSend→UIのActor CoreでDoを呼び出す→Doでユーザーイベントを発生させる→イベントストラクチャでStatusを更新…という結構ややこしい経路をたどっています。それでも個人的にはユーザーイベントで表示器を更新するのが好みです。

次に、Start Acquisitionの処理を作成します。これはSupervisorがメッセージを受け取るため、メッセージ用のVIはSupervisorで作ります。Start Acquisitionでは、DAQ AIアクターにしきい値を送る必要があるため、まずはSupervisorにしきい値を送るためのVIを作成します。

Supervisor Start Acquisition

ここの作り方は私の好みです。これがSupervisorのDoで呼ばれるわけで、Supervisorクラスにキューをプライベートメンバーにいれてここから直接DAQ AIアクターやDAQ DIアクターに送る設計も有効ですが、受け取ったメッセージ(必要ならそれを処理した結果)をイベントストラクチャに返すのが私の好みです。これに関しても、アクターメッセージを作成します。メッセージクラスを作成したら、Send ~のVIをUIのActor Coreに置くことで、メッセージをSupervisorに送ることができます。

UI Actor Core Final

最終的にこうなりました。ButtonがTrueの(Start Acquisitionが押された)ときに、アクターのCallerにメッセージを送ります。この場合のCallerはSupervisorになります。Stop Acquisitionと画面を閉じたときのメッセージも同様にSupervisorアクターに作っています。

DAQ AIアクターの作成

DAQ AIアクターでは次のことを行います。

  • SupervisorからDAQ物理チャンネルを受け取ってタスク作成→チャンネル設定をする。
  • SupervisorからのStart Acquisitionの指示を受け取ってデータ集録を開始する。
  • データ集録中は2秒ごとにオンデマンドで波形をSupervisorに返す。
  • Stop Acquisitionの指示でデータ集録を終了する。
  • 波形のどこか1点以上がしきい値を超えたらSupervisorにしきい値を超えたことを伝える。
  • UIを閉じるとDAQタスクを終了して自身のアクターを終了する。

Pre Launch Init、Stop Core、UninitはUIと同様に、それぞれ初期化、停止イベントの生成、終了処理を行います。VIの中身もUIアクターの同名のものとほとんど同じです。そのため、ここでは各メソッドやメッセージの作り方の紹介は省略します。

ただ、このアクターでは周期的に波形を集録する必要があります。それを実現するためには、イベント処理ループにあるイベントストラクチャのタイムアウトを利用してもいいですが、今回は「Time-Delayed Send Message.vi」を利用します。これは、あらかじめ作ったメッセージの処理を繰り返し行うためのものです。Start Acquisition.viのメソッドで集録開始時にこれを呼び出します。

Time-Delayed Send Message

このVIの真ん中あたりにあるのが「Time-Delayed Send Message.vi」です。これに繰り返し処理をしたいアクターのキュー(今回は自分自身のキュー)、繰り返し処理をするメソッドに対応するメッセージ(今回はAcquire Waveformメッセージ。これは波形を取得するもので別途自作)、繰り返し周期、必要なら繰り返し回数を入力すると、対応する処理を繰り返し行います。繰り返し回数の指定がない場合は無限に繰り返します。

ただ、繰り返し処理を止められないと困ります。繰り返し回数が不定で、ユーザーが指定したタイミングで止めるには、Delivery Notifierを利用します。これはTime-Delayed Send Message.viの中で作られるノーティフィケーションです。今回は、Stop Acquisition.viの中に繰り返し処理を止める処理を入れます。

image

Delivery Notifierに「Stop all further copies」を入れていることで、Time-Delayed Send Message.viの繰り返し処理が終わります。このときにDelivery Notifierの破棄もTime-Delayed Send Message.viの中で行われます。

DAQ DIアクターの作成

DAQ DIアクターでは次のことを行います。

  • SupervisorからDAQデジタルチャンネルを受け取ってタスク作成→チャンネル設定をする。
  • Supervisorからのしきい値を超えたことを伝えられたらデジタル入力値を返す。
  • UIを閉じるとDAQタスクを終了して自身のアクターを終了する。

メッセージやメソッドの作り方はほかのアクターと同じなので、説明は省略します。

Supervisorアクターの作成

Supervisorアクターでは次の処理を行います。

  • 子アクターの起動
  • 設定ファイルの読み取り+子アクターへの送信
  • UIアクターからDAQのアクターに集録開始/終了の指示
  • DAQアクターからUIアクターに集録結果の送信
  • しきい値を超えたときにDAQデジタル値を取得し、UIアクターに送信
  • UIアクターの画面が閉じられたらすべてのアクターを停止
  • エラー処理

メッセージの作り方などはほかのアクターと同様ですが、Supervisorでは子アクターを起動しなければいけません。その処理はActor Core.viで行います。Pre Launch Init.viで子アクターを起動するとバグるようなので、Actor Core.viの最初のほうで行います。

Supervisor Actor Core

これがSupervisorのActor Core.viです。ForループでUIアクター、DAQ AIアクター、DAQ DIアクターをSupervisorアクターの子アクターとして起動しています。起動する際にActor Coreのフロントパネルを開くかを設定できます…が、これは罠です。この入力端子には何もつなげない(またはデフォルト設定のFalseをつなげる)ことを推奨します。開発環境ならTrueをつないでもいいですが、実行環境(exeなどランタイムで実行する場合)で困ります。画面を開く必要のあるActor Coreについては、該当するActor Coreの中で開くことをおすすめします。Supervisorアクターでは複数のキューを使って各アクターにメッセージを送る必要があるので、ここでは各キューをタイプ定義クラスタにまとめています。また、子アクターを起動してから自身のメッセージを受け取るループに入る間に子アクターの初期パラメーターの送信をしています。

Supervisorでは、エラー処理も行います。子アクターではアクターの終了時にHandle Last Ark Core.viで親アクターにエラーを通知していますが、親でエラー処理をしなければいけません。エラー処理をするには、Handle Error.viをオーバーライドします。今回はシンプルにエラーダイアログを表示します。

Error Handle

ここではシンプルエラー処理を追加しています。個別にエラー処理をする必要がある場合は、ケースストラクチャを使うといいと思います。また、デフォルトではstop actor?にTrueを出力してアクターを終了するので、エラーを適切に処理した後にアプリケーションを継続させる場合はstop actor?の結果を変える必要があります。継続させる場合は、子アクターが終了しないように注意する必要があります。

Launcherの作成

最後に、Supervisorアクターを呼び出すVIを作成します。これは実は簡単です。

Launch Supervisor

これだけです。もっと言うと、右のFP.OpenはLauncherの画面を隠すための処理なので、本質的にはSupervisorアクターのクラスをLaunch Root Actor.viにつなげるだけです。下のパラメーターFalseは、入力したアクターのフロントパネルを表示するかを決めるものですが、これはFalseにするか未配線にすることを推奨します。ルートアクターの画面を開く場合でもTrueにするのはおすすめしません。ルートアクターのActor Coreで開く処理を作ることを推奨します。ここをTrueにしてランタイムで(exeなどで)実行するとエラーが発生する上に、そのエラーは検出されることなくプログラムが終了します。開発環境ではうまくいくのに実行環境にした途端に動かないうえに何で動かないか通知されないという困った事態になります。

最終的には、こんなプロジェクトになりました。

image

かなりファイル数が多いですし、階層もかなりあります。

実行

これで完成したので、アプリケーションを実行します。実行するVIはLauncher.viです。

Run VI

…ちゃんと動きました。このダイアログを閉じてからStatusが更新されるのは微妙ですが、まあいいでしょう。

ソースコード

今回作成したサンプルコードです。

GitHub - taacranzk/TN_Actor_Framework_Sample: LabVIEWのActor Frameworkを用いたサンプルコードです。
LabVIEWのActor Frameworkを用いたサンプルコードです。. Contribute to taacranzk/TN_Actor_Framework_Sample development by creating an acco...
(Github)

このコードを作成したLabVIEWバージョンは2026 Q1です。20.0で保存しているので2020以降なら実行できると思いますが、実際に2020などで編集や実行できるかは確認していないのでご了承ください。

また、DAQを使用する場合は、アナログ入力のある実機を使用するかシミュレーションデバイスを設定する必要があります。実機もシミュレーションデバイスもない場合は、LabVIEWプロジェクトの一番上のプロジェクトで右クリック→プロパティで条件無効シンボル(Conditional Disable Symbol)を変更することで、DAQなしで動作させることができます。具体的には、条件無効シンボルの「NoDAQ」のValuesを「TRUE」(すべて大文字)に変更することで、DAQの処理を無効にして乱数を発生させるようにできます。

Conditional Disable Symbol

まとめ/感想

Actor Frameworkは大規模なシステムを想定したデザインパターン、あるいは、フレームワークです。そのため、アクターの関係性をどうするか、どんなメソッドを持たせるか、メッセージを送る際に必要なパラメーターは何か…などなど、ちゃんと設計をしてからコードを作成するべきものです。今回の例ではそんな複雑な処理をしないから作りながら考えればいいだろうと思って作ると、メッセージのパラメーターが足りなかったり、アクターのプライベートデータが足りなかったりすることがあります。

あと、Actor Frameworkはデバッグがしにくいと言われます。それは、アクターの起動が非同期的かつ動的に行われていること、メッセージ処理のDo.viのAllow Debuggingの初期設定が無効になっていることなど、原因はいろいろあります。実際のところ、今回の例でも何回もエラーが発生してプログラムを修正しています。

それと、ユーザーイベントにこだわりすぎたかなと思います。アクターのプライベートデータにCallerやSelfに送るキューを入れることで、ユーザーイベントを介さずにDo.viでメッセージングができます。ユーザーイベントを使うとデバッグ時に画面表示ができるという利点もありますが、パラメーターを追加するたびにユーザーイベントの作成や破棄の処理を追加するのは手間でした。Actor Frameworkはデバッグしにくいので、デバッグを優先するか、指示系統を複雑化しすぎないようにするか、バランスが難しいですね。

あと、Launch Root Actor.viとLaunch Nested Actor.viに「Open Actor Core front panel? (F)」という端子があり、ここでActor Coreの画面を開くか制御できそうですが、これはひどい罠だと感じました。開発環境では期待通り動きますが、ビルドして作った実行形式で実行するとエラーが発生します。Nested ActorのほうはまだCallerにLast Ackメッセージを送ってエラーをCallerに通知するのでいいですが、Root Actorではこのエラーを通知することができません。できません。このエラーをLauncherで受け取れるようにするにはActor.viを改造する必要があります。この機能は封印したほうがいいと感じました。

ひと言でいうと、「疲れる」でした。

タイトルとURLをコピーしました