LabVIEW 6.1 is arguably one of the most important versions of everyone’s favourite graphical programming language. Sure, 8.2 delivered Object Orient Programming to us “wire workers”, and 5 brought us the Undo feature, but 6.1 introduced one feature (and expanded in later versions) that has dramatically changed the way many of us do LabVIEW programming.
This feature is the Event Structure. I’ll start by saying I am not overly keen on its name. I like to think of it as the event handling structure or event handler, but let’s not get into the semantics just yet.
The event structure, as it appeared in 6.1, let us bind static events such as as a mouse click or control value change with some action defined in a case within the event structure. When I say static events, I’m referring to the fact that the event handler case and its causal activity are defined at edit time and are valid for the entire execution.
For many of us, the power of these static events was immediately apparent; no more “polling” for value changes of boolean controls!
Our only option for an event-driven user interface before 6.1 was to check which of the Boolean controls had been selected by constantly polling. This is far from optimal, and 6.1 gave us the ability to simply bind an event case to each boolean control’s value change event as shown below:
The coverage of these static events grew further over the next few versions of LabVIEW. Those eagle-eyed amongst you who were using 6.1 may have spotted a greyed-out option when popping up (right click menu) on a event structure boundary, this option read “Show Dynamic Terminal”.
LabVIEW 7.0 un-greyed that option and gave us User Events.
I am not a huge fan of the name “User Event” either. Who is the user ? Me (the developer) ? The end user running the software ? I prefer the simple term “custom events” or “dynamic events”.
I remember the original shipping examples didn’t do a great deal to promote them and much of the discussions on LAVA and InfoLabVIEW about this new feature were really quite underwhelming. Also, the event structure was not part of LabVIEW base for a very long time.
I did not really understand the significance of User Events until 2010 or 2011. I was working on a large test application at the time and needed a way of opting in and out of a data subscription. The ability to register and un-register for events seemed perfect for this application, and so I started to use them in this limited way. Then I attended the 2011 CLA Summit in Austin and watched Justin Goeres talk about Private and Public user events. I was sold!
I immediately returned home and started to think about them as the primary message transport mechanism within my application architecture (which at the time was a QMH using a queue pair) but I’m getting ahead of myself. Let’s go back and re-familiarise ourselves with user events first.
Starting with the palette:
We can think of this palette in two halves. The bottom row is concerned with the publishing of events. We define or “create” an event, We generate the event at some point during our application execution and then destroy the event, usually when we are done and are exiting our application.
The top row is very much concerned with the subscription to events. We register to “hear” about an event occurring (but don’t necessarily do anything about it) using the Register For Events node.
We can then bind these dynamic events to an event structure by wiring the output of the the Register For Events node to the Dynamic terminal.
Now when our event is fired, we hear about it and handle it.
Let’s look at how we might implement this API on a block diagram:
The first thing to show is that the events API effectively decouples the Publisher and the Subscriber by using a Register For Event node. This takes an event (or several events) refnum as an input(s) and registers for those events, and then outputs an event registration refnum. Our event producer is now decoupled from the consumer of those events. As you can see in the image above, the API of these two activities is different, because the lifetime of the two refnums is different. Destroying the Event Registration Refnum in one Subscriber does not prevent the Publishing and Subscription of events elsewhere in our application.
One thing to be mindful of is the lifetime of the Event reference itself. This is bound to the top level VI in the hierarchy in which it was created…….wow, what does that mean? Well, if you’re using an FGV to share the reference between two VIs, and the Block Diagram on which the Event was created stops executing, then the Event refnum is cleaned up by LabVIEW even though it is being held in memory by our FGV. Anything using that refnum will return an error from that point onwards.
This makes sense to me, since an event does not have a reason to exist when its owner has stopped running. Fab told me it took her a while to understand this, because for her the owner of the event was the FGV itself and not the VI that had called the FGV to create the event.
This simple video demonstrates what I’m talking about:
So to be clear, even if we moved the event creation from the launcher into the FGV, when the Launcher stopped executing the reference will still be destroyed as the Launcher is the top level VI of the hierarchy in which the event was created.
It is a point of contention where the Register For Events node should sit in this model. If it resides in the Subscriber, then the Publisher’s user event refnum is being exposed to the Subscriber, which is potentially undesirable.
However, if we put the node in the publisher, then the Event registration refnum is now being passed across VI boundaries leading to terminal mutation issues (where the data type of the User Event changes the Event Registration Refnum) or incorrect forking of the Event Registration Refnum wire (I’ll be talking more about this in a future post, for now let’s just assume it’s bad).
Let’s stay with the above model for now, since the benefits outweigh the drawbacks.
We have these two different APIs depending on where we sit within this model. Contrast that with the Queue API where the Subscriber and Publisher have a common API and share the lifetime of the Queue Refnum. If one were to destroy the Queue reference anywhere in the hierarchy, then all Subscribers or Publishers of data to that Queue would be affected.
This makes Events a great way of communicating between multiple independent asynchronous processes, which is why we use them at Delacor, and why we chose them as the primary message transport mechanism in our application framework, the Delacor Queued Message Handler.
I could go on all day talking about events, and will surely revisit them in a future post.