Published December 6, 2015
Actors are a powerful tool when creating large, highly parallelized, scalable systems in LabVIEW, but the framework takes some effort to conceptualize and therefore takes some time to learn. This series of articles will walk you through all of the major concepts in the Actor Framework world and get you to a point where you can start building and using Actor Framework applications.
- Actor Framework Basics: Part 1 - The Basics
- Actor Framework Basics: Part 2 - The Actor
- Actor Framework Basics: Part 3 - Launching and Communicating
- Actor Framework Basics: Part 4 - Being Productive With Actors
- Actor Framework Basics: Walkthrough - Creating a Logger Actor
Before we get into the Actor Framework, let’s take a look at its ancestor:
The QMH design pattern has been explained in depth by NI (for example here and here), as well as by many folks in the community. There’s even a QMH Project Template that ships with LabVIEW. Here’s what the important bit ends up looking like:
- The Data: This is the typedef’d cluster on the shift register. Everything about the internal state of the QMH is stored here.
- The Queue: This is how you get data into the QMH and how you tell it to actually do something.
- The Message Handler: This is the “thread” that the QMH executes on. This keeps running until the QMH decides that it’s done. This is a while loop with a case structure in it.
- The Messages: Each message has an identifier (the string in the image above) and some Data (the variant)
So why is this design pattern so popular?
- ** Data Encapsulation**. That means that no other parts of code can come in and put you in an invalid state. If they want to affect the state, they have to send the QMH a message. This means that me, the developer of the QMH, the person who has in depth knowledge about what’s OK and not OK for my code to do, can define everything that anyone else can ask me to do.
- Separate Thread Execution. LabVIEW is really good at helping us develop multi-threaded applications. If we need a new thread to execute something, we can just implement it as a QMH, then send it messages and get whatever we need done.
But now for the bad.
- The Open Queue: Passing a basic LV queue around is a bad idea. What if something destroys the queue? What if something decides it’s message is more important, so it enqueues it at the beginning instead of the end (or worse, it flushes the queue first!). There are a lot of ways for someone who’s using your QMH to get in there and mess with what’s going on.
- Loosely Typed Messages: Almost all QMH’s use a message ID field (usually an enum, or a string) and a Data field (usually a variant). The first thing the QMH does in its message handling code is convert the Data variant into a useful type. This means that the code sending you a message has to know a little bit about what you’re going to do. It has to get the variant type correct. If you change the message data, you need to make sure you remember to go and update any code sending your QMH a message.
- Creating New Threads Isn’t Handled by the Framework: If you want 1 QMH, you drop the code in 1 spot. If you want 2 of the same QMHs, you drop the code in two spots. If you want 3….. you get the point. What if you want a lot of the same QMHs, or if you don’t know how many you want until runtime? It would require a decent amount of VI Server work that’s finicky to get right.
- Not Extendable: What if you wanted a new QMH that was exactly like this other QMH, only it handled a new message? Maybe you wanted 2 QMHs that were exactly the same, except the way they handled this one message. To do this you’d maybe copy and paste code (but now you need to fix bugs in two spots) or maybe add an enum to the state data (but maybe they needed their own different state data)
If you’re familiar with Object Oriented Programming (if not, check out this white paper from NI or maybe take this Training Class) then you should have some ideas of how some of those cons can be addressed. Using an Object Oriented (OO) approach, we can address all of these problems:
- Encapsulate the Queue: Instead of passing the queue around, we’ll pass a class around. This class will allow other pieces of code to message the QMH. The guts of how this works will be abstracted from everyone, but most importantly, we’ll be very careful about what we allow people to do with the queue. It’ll be impossible to flush the queue or do any of that other bad stuff. They’ll only be able to enqueue at the correct end.
- Strictly Type the Messages: Create a message class, and every message will inherit from this class. Each message class will have its own private date and constructor and such, so now we’ve eliminated the variant. Every message will always have the right kind of data with it. If we change the data associated with the message, everyone who uses the message will automatically be broken until they update their code.
- Take Care of Creating New Threads: This is a little trickier, but if we carefully design our OO QMH we should be able to write the VIs that create the new threads generically so every QMH you have will be launched in the same way. This means you only have to debug the code once, and then you can forget about it.
- Make each QMH an extendable class: Use all the normal OO design patterns and tricks when designing your QMH. This will make it easily extendable as needed.
The cons about our OO QMH? There’re a lot of classes to create. It’s a non-trivial amount of code for just the framework. This takes a pretty good amount of time to create and even longer to debug. A big thing to notice with our OO QMH: The Queue (which is how messages get to the QMH) is a different class than the QMH class. This is very important. It’s very tempting to want to merge these two classes. That would mean that you’re passing around a class wire that contains state data (but not the “right” state data) to people who really want to send your QMH a message. This makes it confusing for code using your QMH. They’ll have a wire has methods like “Perform Action” and “Send Perform Action”. How are they supposed to know which version to use where? Now if only someone would write all of that for us…..