This is a long-overdue continuation of my post on May 8th regarding the use of plugins – an important piece of the puzzle when managing plugins is communication to and from them and in this entry I’d like to focus on how communication can be accomplished through the use of user events.
Open any sufficiently complex LabVIEW application, and you’re likely to see more than one while loop. From an organizational standpoint, most programmers consider this an easy way to visually de-couple segments of their code that perform independent, asynchronous tasks. It’s also important to note that data-independent loops will automatically utilize multithreading, which makes it possible to take advantage of multicore hardware for the sake of performance and responsiveness. (Note: Two loops are data-independent if neither has an input terminal that depends upon the output terminal of the other).
As important as multiple loops are to most applications, many programmers still struggle to select the correct approach for communication between separate processes. This is often magnified as the application becomes larger and more complex, where a highly-scalable, and highly recognizable solution becomes critical. As with many things in LabVIEW, there are numerous technologies that can be used to solve this problem… in fact, a recent estimation put the count just under 25 (I challenge those of you reading to name all of them, including third-party solutions).
At the risk of grossly over generalizing, one of the most popular solutions for both sending both commands and data streams amongst LabVIEW users are queues, but in this article I would like to highlight how dynamically registered events can be used as an alternative. It’s especially applicable when managing plugins, as you’ll likely want to broadcast a message to an arbitrary number of registered listeners. Consider how you tell all of the plugins to STOP! If you were using a queue, you would need to create and manage a separate queue for every single recipient (remember, a queue can have multiple producers, but should only ever have one consumer). Instead of creating and managing an arbitraty (and potentiall large) number of queue references, we would like to be able to broadcast a message to an arbitrary number of registered listeners. As you may have already guessed, we can do this with a user event.
The creation and generation of a user event is fairly simple. Like many things in LabVIEW, you create the user event, generate the event when you want to send the message, and eventually destroy it. This event will be handled by any and all Event Structures that have been configured to listen to registered events. The image below shows an illustration of how this is setup. Notice that the event structure has a terminal input known as the ‘Dynamic Events Terminal’ where these user events are registered (if you do not see this on your event structure, right-click on the frame and select ‘show dynamic events terminal’. Because this scenario utilizes an Event Structure, it’s an especially useful mechanism for communication with separate ‘things’ (ie: a Plugin), which may already have a user interface and therefore already be utilizing an event structure. For an actual example of this approach in use with plugins, check out Exercise 5 of theObject Oriented Design Patterns Technical Manual.
Once the registered events have been wired into the Event Structure, you’ll have a new list of options to select from when editing the list of events that the structure handles. Ideally, these commands would be pre-defined by the framework or the calling application and the user would be able to choose if and how to handle these events. The OO Technical Manual example passes all the registered events to the plugins through an API that completely hides how and when these events are created and generated. Some (such as ‘shutdown’) are broadcase, while others are only sent to one specific listener, but this complexity is hidden from the developer responsible for writing the plugin.
One registered, every User Event will appear as an option that can be selected and handled (as shown above), but one of the limitations to this approach is that you have to define the behavior for every single listener and you have to manually configure the event structure to handle that particular user event. However, this can be overcome thanks to the fact that user events can pass data – more importantly, the data can be an Object!
When combined with the command pattern (also mentioned in the OO technical manual), we can create a hierarchy of commands that can potentially be sent to any and all registered listeners. This allows us to easily add new commands and define their behavior without even having to modify the individual plugins. All we need is one event in the structure that dynamically dispatches a method of this class hierarchy that contains the command-specific behavior.
The example below shows how we could send a stop command using this approach, where ‘Stop Commpand.lvclass’ is a child of ‘UI Command Manager.lvclass,’ and ‘Execute Command.vi’ is a dynamically dispatched VI that belongs to this class hierachy.
I’ve become a big fan of user events to broadcast messages, and (in case you couldn’t tell), especially when I need to make it easy to send messages to plugins that need to remain very de-coupled from the calling application. This is not the only approach and doesn’t necessarily suit use cases where you need to stream large amounts of data at a high-rate. For this purpose, consider some sort of a FIFO (Queues for local communication, RT FIFOs when it needs to be deterministic, and Network Streams for target to target).
Another approach for async communication between independent processes is the Actor Framework