One of the key architectural rules I learnt in LabVIEW is what I always call in my head the “single source of synchronisation” rule. I need to make two opening statements on this:
- Someone/Something taught me this – Huge apologies I can’t remember the source of this but thank you whoever you are!
- Its name makes something simple sound very complicated; it isn’t!
It made an appearance in my post on architectural language so I wanted to expand on it. The quote there was:
A loop within an application component. It has a single reason to run.
Why Loops Run
By “run” what I mean is what triggers an iteration of that loop. I’ve identified these sources:
- Time – Intended to run every x ms.
- Event – Either a UI or User Event, the loop contains an event structure.
- Data – Basically a queued message handler but “data” means anything that means a loop waits for data. A data interface could be queues, notifiers, streams, DAQmx etc. Some external process is forcing us to wait for new data to be available.
The rule means, if one loop is trying to use two of these methods (or multiples of one of these types) then problems may occur. Examples might be:
- Using the timeout on an event structure
(did you know it won’t fire if user events fire, even if you aren’t handling them? Thanks Chris R for this demo!)Thanks Fab for the correction: This was a bug that has been fixed.
- Using the timeout of a queue to perform a repetitive action.
As you can see – time is usually the conflict because the others are kind of obvious to avoid having in the same loop.
Why This Rule Exists
Put simply, in most cases where two of these exist there is a conflict which is hard to resolve consistently 100% of the time, mostly because the event and data drivers depend on external components which aren’t predictable.
Take the timeout case of the queued message handler. If you want to check a state about every 1 second, then the best case is a new external message once every 2 seconds. Then 1 second after that message arrives you perform the check. Maybe you will get a second check before the next message, maybe not. Most likely you will end up performing the check every 2 seconds. Perhaps that is acceptable?
However then an external component generates messages every 0.1 seconds. Now that check doesn’t happen ever, through no fault of yours! (In the context of the code in this loop).
But I Need To…
Perhaps there is a case where you want to break this rule. I have two solutions:
Use a second loop as a “proxy”. For example in the case above have a second loop which runs every 1 seconds and enqueues a message for the check.
Another example is when you have an API which generates messages on an event but you need it in a queue. If you have used the actor framework, this how you extend actor core for GUIs.
Break The Rule… And Be Very Careful
If you don’t see a way to design around this rule, then you need to know you are breaking it and be very careful. Most likely by adding some management code to a loop.
The example I could think of was if you needed data from two loops. In this case, you would write code to read from each queue. If one read timed out, perhaps you store the successful data and then try again, reading only the failed queue.
I don’t think there are many cases, but rules are about knowing when you break them and why!