This post was originally published at Walking the Wires
A few months ago I published this blog post about User Events in LabVIEW. From the replies and questions that this sparked, it is clear that many people either use LabVIEW User Events as the primary message transport in their applications or are planning to do so in the future, and that the concept of multiple asynchronous “actors” is something which resonates positively with many developers as a great way to architect an application.
That blog post, along with the subsequent launch of our own User Events-based application framework, the DQMH, raised many questions, such as “Who should define (create) the events?”, “How and where should I register for events?” and “How do I register for events in multiple locations?”
It seems like we need to explain a little of the rationale of how we handle event registration in the DQMH (and why) and hopefully along the way we will answer some of the above questions.
One of the first decisions that we had to make was with regard to the creation of events. In the DQMH we refer to inbound events as request events. These are events that originate from outside a module requesting that the module do something. Outbound events are referred to as broadcast events, and other modules can register to receive them.
We debated long and hard about where these events should be defined (and created) because, as explained in our earlier blog post, the lifetime of the user events is bound to the hierarchy in which they were created. Should the caller create the events related to another module? Should the module be responsible for the lifetime of its own events? Does it make sense to be able to fire an event for a module that is no longer in memory? What happens if the launcher goes offline; do we have a zombie to take care of?
These were many of the questions we asked ourselves.
Ultimately the decision was made based on our desire to be able to interact with our asynchronously running modules from TesStand. If we had chosen to have the events being defined in a hierarchy outside of the module, say a LabVIEW based TestStand step called Start Module.vi, then as soon as the TestStand execution moves to the next step in the test sequence, the User Event references would become invalid and we would lose our ability to communicate with our module using these events.
Therefore, we decided that a module should create (and thus own) its own events. We would then return the events as part of the Start Module method. In addition, we would provide a public method allowing other modules and code to register for the target modules events.
This is the Start Module.vi code:
The really interesting parts to this VI are highlighted in red. We have a VI called Wait on Module Sync.vi (the turquoise one) that effectively blocks until the asynchronously running module has created it’s own broadcast events (and bound their lifetime to its own). Internally we use a rendezvous to achieve this functionality. Once a module has created its own broadcast events we can then call Obtain Broadcast events.vi that returns the broadcast events that we can then register for.
Internally Obtain Broadcast Events.vi utilises the functional global variables design pattern.
When called by the code above, the Obtain Broadcast Events.vi executes the following block diagram:
We can do this because the blocking call, Wait On Module Sync.vi, guarantees that the module will have already called the above VI and executed the True case of Obtain Broadcast Events.vi as shown below:
The Obtain Broadcast Events.vi is a private VI, meaning that it can only be called by members of the same library. Fortunately, the Start Module.vi is a public VI meaning we can call it from wherever we want to in our own application.
NOTE: The eagle eyed amongst you will have spotted a few things in the above VI, like the seemingly redundant flat sequence structure. Don’t panic! We haven’t forgotten how to program in LabVIEW! We have just put a few things in there to aid with the extensive scripting tools that the DQMH toolkit provides. The VI above is not designed to be edited by hand, instead please use the scripting tools that we provide. We are confident you’ll love using them.
However, not only the launcher of a module may want to register for its broadcast events, other modules may want to do so too. For this we provided a public wrapper around Obtain Broadcast Events called Obtain Broadcast Events For Registration.vi
The great thing about this is that it allows us to register for events (and therefore define event handling cases) even if the module is not running. Registering for what is effectively a null refnum is totally acceptable, and then we can re-register for the actual valid refnums at some other time when we know the module is running, as shown below. Obviously if we call the start module method, this returns the broadcast events for that module.
In the block diagram below we are obtaining the broadcast events for registration (on the left side of the diagram) and registering for them. This allows us to define the event handling cases. In the example below we have defined a case where we can click a boolean to start a new module instance. This calls the Start Module method VI.
Start Module.vi returns the broadcast events for that module (after creating them first) and we then register for them.
Hopefully this gives you a brief insight into how we’re handling event registration in the DQMH template and shows the benefit of using such a framework. It should greatly reduce the amount of code you need to write, allowing you to concentrate on the problem at hand.
Happy wiring folks!
Thanks for posting this great article. I had discovered that my references weren’t working to receive broadcasts but didn’t understand why or how to fix the problem. The exposition of how the sync works and the solution offered here (running start module after broadcasters are known to be up) works great of course. It wasn’t clear to me I needed to do that, or that it was an acceptable way to handle coordination, now I know it is.