In my current role I have the opportunity to use a variety of tools to solve the problems I am presented with at work. While LabVIEW and TestStand were my key value propositions as a new hire, I quickly found myself gravitating toward C# as my tool of choice. This is in no small part due to the clunky nature of interacting with ActiveX APIs in LabVIEW.
After spending some time programming in C# I have found a number of things about it that I really enjoy. There are extraordinarily useful features of the .NET framework and C# itself that get me excited to tackle complex problems. Some features of .NET/C# that I miss in LabVIEW include include generics, lambdas, LINQ and interfaces.
However, my experience in LabVIEW had taught me to approach problems in a way that began to challenge my ability to develop in C#. Dataflow and parallelism as core elements of LabVIEW development had caused me to think of every problem as an opportunity for concurrent operations. As I began writing more "traditional" code in C# I was immediately looking for ways to create parallel branches of execution and spawn asynchronous processes. This can be daunting when first learning about concurrency in traditional text based languages, however my LabVIEW experience gave me the tools I needed to jump right in.
Text based languages are naturally suited toward procedural programming but concurrent programming can be a much more difficult proposition. It is both difficult to reason about and to visualize concurrent software structure in a text based language. LabVIEW solves this problem with it's graphical element; in my opinion this is one of the true powers of the language. It makes concurrent programming accessible to even novice language users.
To explore the differences in concurrent programming in C# vs LabVIEW let's first look at the classic example of LabVIEW concurrency in action, the producer-consumer design pattern.
This simple architecture is one of the first a novice LabVIEW programmer will learn on the path to good LabVIEW coding practice. Despite the simplicity of this diagram, the application structure is actually quite complex. Few introductory programming courses for text based programming would have a novice building an application with this structure and for good reason.
Let's break it down. The primary things to notice here are the following:
Implementing this producer-consumer pattern C# is not a huge leap but it is certainly more difficult to visualize, and in my opinion, is therefore less accessible to a newbie programmer. Let's take a look at the C# implementation.
namespace Example { using System; using System.Collections.Generic; using System.Collections.Concurrent; using System.Threading; /// <summary> /// Application entrypoint /// </summary> public class ProducerConsumerMain { /// <summary> /// Here I create a concurrent queue for thread safe /// data comms between producer and consumer /// I create the consumer as a function running /// in a thread parallel to main /// The producer is simply a function that sends a number /// of messages, including the consumer stop signal /// </summary> /// <param name="args"></param> static void Main(string[] args) { // Create a queue to pass messages between producer and consumer // We use a concurrent queue to account for thread safety issues ConcurrentQueue<TextMessage> queue = new ConcurrentQueue<TextMessage>(); // Create a thread to run our consumer in parallel to this // thread (main) // I am using a lambda expression here to define the // thread's entrypoint and pass it the queue // This is because the default function signature for threads is // static void <function_name>() // or static void <function_name>( object params ) // Using a lambda makes it easy to pass the queue in directly Thread consumer = new Thread( _ => RunConsumer(queue) ); consumer.Start(); // Run the function acting as our producer RunProducer(queue); // The stop message sent by the producer // should cause the consumer loop to terminate // so we join on the finished thread // If the application hangs here // we know the consumer did not stop properly consumer.Join(); // Wait for user to close the console Console.WriteLine("Press any key to continue..."); Console.ReadKey(); } /// <summary> /// This is our consumer loop, it will run until it /// gets a message to stop /// Otherwise it just prints the message it received /// </summary> static void RunConsumer(ConcurrentQueue<TextMessage> queue) { // Run until told to stop bool stop = false; // Loop on receiving data until we are told to stop // by the producer while( !stop ) { // Create a new TextMessage to store data sent // by the producer TextMessage message; if(queue.TryDequeue(out message)) { // Here we would run our consumer logic // For instance, we could use the object message to // dispatch the execution of a particular state in a // state machine architecture // Here we just see if it is a stop message // if not we just print the message if (message.MessageText == "Stop") { stop = true; Console.WriteLine("Consumer shutting down"); } else { Console.WriteLine(message.MessageText); } } } } /// <summary> /// This is our producer loop, it will just loop through a /// predefined list of messages to send the consumer /// The message list includes telling the consumer to stop /// </summary> /// <param name="queue"></param> static void RunProducer(ConcurrentQueue<TextMessage> queue) { // Define some messages to send to the consumer List<TextMessage> messages = new List<TextMessage>() { new TextMessage(), new TextMessage("I'm a message!"), new TextMessage("Goodbye Cruel World!"), new TextMessage("Stop") }; // Loop through each message and send it to the consumer foreach( TextMessage message in messages ) { queue.Enqueue(message); } } } /// <summary> /// The TextMessage class represents a simple data structure /// for passing string messages between producer and consumer /// </summary> public class TextMessage { /// <summary> /// Store the text of this TextMessage /// </summary> private string _messageText; public string MessageText { get { return _messageText; } } /// <summary> /// Create a new TextMessage with a default message /// </summary> public TextMessage() { _messageText = "Hello world!"; } /// <summary> /// Create a new TextMessage with an arbitrary message /// </summary> /// <param name="messageText"></param> public TextMessage( string messageText ) { _messageText = messageText; } } }
As you can see, there are certainly more details for the programmer to be aware of in the C# example. The downside of the LabVIEW approach is that working at that level of abstraction removes the flexibility to do more advanced things. For instance you lose the choice of whether to use a simple queue versus a concurrent queue versus your own queue class. You're kind of stuck with NI's implementation of the queue.
Moving from designing software in LabVIEW to doing so in C# has taught me that LabVIEW is an incredibly effective tool for teaching high-level software architecture concepts. I came out of my development experience being able to immediately grasp the concepts of concurrent programming in C#. That knowledge is also is much more accessible due to it's graphical nature. I have gained an extensive understanding of software design patterns, system architectures, messaging strategies, FPGA and RT design considerations and many more software engineering concepts through LabVIEW. The icing on the cake is that I was able to gain all that experience in just 3 years of my professional experience with the tool.
Owen Search is an Avionics Test Engineer at SpaceX. He is a Certified LabVIEW Architect, Certified TestStand Architect, Certified LabVIEW Embedded Systems Developer, Certified LabWindows/CVI Developer and NI Certified Professional Instructor. C# is his current language of choice but he has a soft spot for C/C++ and a love of LabVIEW. He holds a BS in Biomedical Engineering from the University of Connecticut.
7 Comments
Bob Hamburger
5/19/2016 08:42:10 am
The longer I program in LabVIEW, the more I view parallel asynchronous programming as an (sometimes necessary) evil, to be used sparingly and only when a strong design case for it can be justified. I have encountered far too many applications in which the developers have jumped headlong into a maze of parallel loops with little thought of critical issues such as determinism, data integrity, race conditions, priority inversion, etc. Debugging a large application with many parallel processes is like being at a cocktail party and trying to follow a dozen conversations around you when everyone is talking at the same time.
Reply
Owen J Search
5/19/2016 11:51:03 am
All of those concerns are exactly the reason why concurrent programming in most languages is looked at something you should do sparingly. I think the general rule of thumb is that if you need stackoverflow to help debug your race condition, you probably shouldn't be threading. It's funny though, concurrency in LabVIEW is such a native concept of the language that the culture around it (in general) seems to send complete opposite message. In LabVIEW sometimes it seems concurrency is the hammer to every nail. After programming in C# for awhile and trying to solve every problem with a new thread, I have taken a step back and begun to look much more critically at using concurrent approaches to problem solving. It really took jumping back onto a text-based language to understand the magnitude of the problems that concurrency can cause. I think that may be one of the downsides of LabVIEW's high level of abstraction. As it makes things easier, it hides those parts of the process that may make a developer think twice before throwing down a parallel loop.
Reply
Jim Fowler
6/6/2016 12:28:42 pm
Nice post, Owen, and always nice to run into you in the community, Bob.
Reply
Toufiq Hossain
11/21/2016 07:56:40 pm
hi Owen Search.
Reply
Toufiq Hossain
11/21/2016 07:56:50 pm
hi Owen Search.
Reply
11/6/2019 08:26:38 pm
I strongly suggest that you look into .NET TPL (Task Parallelism Library) and System.Threading.Tasks.Dataflow. It does provide you Dataflow, concurrencies, buffering and queue that you are looking for. In fact, I developed .NET application that receives telemetry data from 20 devices concurrencies and it performs quite well. The code is small and efficient.
Reply
Leave a Reply. |
Tags
All
Archives
October 2019
LabVIEW Blogs |