LabVIEW Development Best Practices Blog

Community Browser
cancel
Showing results for 
Search instead for 
Did you mean: 

Re: Using Events for Communication Between Asynchronous LabVIEW Loops

Elijah_K
Active Participant

12-15-2010 2-43-10 PM.png

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 the Object Oriented Design Patterns Technical Manual.

9-14-2011 11-46-58 AM.png

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.

9-15-2011 9-55-41 AM.png

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. 

9-15-2011 10-17-04 AM.png

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, which simplifies the process of sending commands and data betwen multiple arbitrary processes, and also makes it possible to inherit and extend the functionality of existing actors.  More on this later!

Elijah Kerry
NI Director, Software Community
Comments
brucexliu
Member

Cool~

This is useful! I am planning to design a user interface, and the long term goal is to make this user interface used as easy as LV block diagram does.

Is it a good idea to use user event to handle the UI commands? I mean, Yes there is a good idea in some cases when we need to add or modify the commands and to use this command anywhere we want. But I have concerns about the performance of user event.

1.     Will it be efficient as the queue does?

2.     Is there anything that I need to pay more attention to when I use this way to send messages?

3.     Is there a better way to do this? Like actor framework?

Thanks,

Xu

Elijah_K
Active Participant

Whether or not you use the Actor Framework is really a decision to made based upon the design of the entire system, as opposed to how you manage a UI, which is just a component of an overall application.

I would recommend looking at this example for a recommended solution for a complex UI: OO Design Patterns Example. The example for this tutorial follow my general recommendations for complex User Interfaces, which are as follows:

1) Always capture user interaction using the event structure

2) Immediately delegate any action that the program needs to take to a the approapriate asyncronous process using a queued command handler

3) For complex UIs, do not copy references to UI components all over your application. Create one single process that has these references and is responsible for updating the Front Panel (it should be separate from the event structure receiving commands).  When something needs updating, send a command to this process with the appropriate information to display.

4) Use sub-panels for any tab-like UI (ie: wizards, options dialogs, configurable displays). Repeat all the steps recommended above for each VI that will be loaded as a sub-panel

And optionally, defer panel updates when redrawing complex UI objects in order to smooth the display rendering

Elijah Kerry
NI Director, Software Community
sth
Trusted Enthusiast Trusted Enthusiast
Trusted Enthusiast

This is something I started doing in my LV 8.5 app.  It has worked but is a little cumbersome.  I think there are some hints there that may be very very useful to cleaning it up.  I realy like the async nature of user events.

But I have thought of trying to make it distributed across the network.  With a loss of timing determinancy it could be very very powerful, but you have to make a TCP/IP receiver/sender to be able to broadcast events to clients and then trigger the events on the clients.  It could be done, but it is a big project.  It would be nice if user events could be registered with a target and/or host for those.

LabVIEW ChampionLabVIEW Channel Wires

Elijah_K
Active Participant

Network communication introduces an entirely different set of challenges and events are really not an appropriate solution - this is not a unique problem for LabVIEW. In fact, almost all forms of network communication are point to point - multiple listeners usually entails multiple connections and most routers don't even truely support multicast packets. If you were to create and send a multicast packet to your home router, it would probably just flood the network instead of properly implementing reverse path forwarding.  Here's a quote from wikipedia's page on IP Multicast:

"While IP multicast has seen some success in each of these areas, IP multicast is not widely deployed and is generally not available as a service for the average end user. There are at least two primary factors for the lack of widespread deployment, both somewhat related to the other. On the one hand, forwarding multicast traffic, particularly for two-way communication, requires a great deal of protocol complexity. On the other hand, there are a number of additional operational concerns in being able to run a multicast network successfully, largely stemming from the complexity of a widely-deployed implementation, not the least of which is the enabling of additional avenues of failure, particularly from denial-of-service attacks"

I recommend the use of Network Streams or STM for network communication, and especially for sending commands.  If you need to communication between many devices, you'll need to establish individual connections for each pairing.

Elijah Kerry
NI Director, Software Community
sth
Trusted Enthusiast Trusted Enthusiast
Trusted Enthusiast

Agreed, but a structure much like the one NI uses for network device discovery would be appropriate.  Use of multicast for the local sub net to ask devices to identify themselves with a point 2 point connection for actual communication.  There are a bunch of zero config options for ways to identify resources on the network such as printers, servers, etc.  This technique could be used to establish which locations are available to handle "network events".  The events themselves once registered over the network could be implemented by UDP.  This works for identifying and use of such resources as PXI and CRio network nodes and could be extended to a user event structure.  In this case the event source would keep a list of addresses for sending events as point-2-point UDP communication, when it wished to signal an event it would send it to the internal list of registered nodes/ports.  The client machine would have a listener waiting on those port(s) and then create the appropriate user event on that machine.

So I am not thinking of such an open loop architecture as just multicasting events, but the same as currently by registering nodes for events.  This is just like registering structures for events within a labview instance.  The trick is to now expand to multiple instances on either the same or remote machines.  Even if you have two built applications they can't share events for communication.

It is a different layer of challenges but events as you show them are greate for inter-thread communication within a single LV program.  This is just brainstorming for a way to work with threads that are less closely coupled in either different execution processes or different machines.

Maybe I am dreaming, I started to make something like this for different processes on a same machine using named pipes but ran into too many bugs with the named pipes in LV 8.5.  Since then I have been toying around in the back of my mind with a way to decouple that inter-thread communication to extend it to different machines entirely.  Maybe I should put this in a different thread, but the analogy to user events is what caught my attention.   Possibly it could be done by a network queue structure but I think of it as more an event driven architecture.

BTW: I agree that a pure IP multicast based architecture would be fragile and not very universal.

LabVIEW ChampionLabVIEW Channel Wires

PaulLotz
Member

With the Datalogging and Supervisory Control (DSC) module you can set up dynamic user events for networked shared variable value change events.  This allows you to use publish-subscribe communications, where the listeners respond to events.  This is what we do.

sth
Trusted Enthusiast Trusted Enthusiast
Trusted Enthusiast

The DSC is not cross platform and the Network Shared variables have had overhead issues and performance problems.  These may have been overcome in the newer releases of the shared variables, but there seems to be no move towards the DSC going multi-platform.

LabVIEW ChampionLabVIEW Channel Wires

PaulLotz
Member

True, DSC is not cross-platform, but it is an option (and a good one, actually) for Windows applications.  (We actually also use the Read Variable with Timeout method from the PSP Variable Palette with VxWorks on a cRIO to provide event-like behavior.  It would be possible to create a user event each time this VI read a new value if you wanted.  That would be an extra step in our case.)

Edit: I thought about it. We actually do just what I said, that is, we publish the output of Read Variable with Timeout to a user event, on a cRIO.  This works great.  (For the record, the type of the event is a top-level Command class, so that we can send any of the various child commands on this event.)