05-21-2019 11:56 AM
Hi everyone
I am writing a VI with arrays of controls in a number of tab control panels. Default values for these arrays will be read from a configuration file. I am using event structures to execute some code when I click on controls. I want my VI to (a) pass the values from the configuration file into the various controls and indicators and (b) let me then edit the values using the controls.
I've been having difficulty getting it to work as intended so I have created a very simple VI to test my code structure.
What is meant to happen is:
The block diagram is as follows. A while loop keeps the code running continuously. When first executed, a timeout value of 0 is passed to the event structure so the TimeOut case runs. If Page 1 is active, the code passes the initial value into Control 1 and Indicator 1 (this works).
The shift register then changes the TimeOut to -1 so the event handler does not time out.
A "Stop" case passes True to the while loop test condition (This bit works too).
If the VI started with Page 2 active, clicking on the page 1 tab should pass "Initial value 1" into Control 1. This does not work at the first attempt - one has to click on Page 1, Page 2, then Page 1 again before the value updates.
With Page 1 active, clicking on Control 1 should toggle its value, with Indicator 1 following suit. This does not work as expected - instead, I need to click twice on Control 1 to make it change. It takes 4 clicks on Control 1 to run through a complete cycle of outputs, like this:
I cannot see why it does this.
Interestingly, the Page 2 code has one wire deleted from the diagram (below) and works perfectly except that it does not pass Initial Value 2 into Control 2.
Help!
Thanks...
Roger
Solved! Go to Solution.
05-21-2019 12:59 PM
Sorry, posting by phone.
It usually helps to put the indicators before the event structure, connected to the wires from the SR. See how far you get.
05-21-2019 02:04 PM - edited 05-21-2019 02:04 PM
Your problem was with data flow. You had no guarantees as to whether the control would be read first or written to first. I moved the initializers outside the loop. Another way to do this would be to make your initialization state separate from the other states. You could then set your values inside the initialization state as you had been doing and never enter that state again.
05-21-2019 02:48 PM - edited 05-21-2019 02:54 PM
[The perils of writing a reply a minute here, a minute there. Other help showed up before I finished. It's a good summary, but I'll leave my stuff below because it explains the details more.]
Thanks for such a clear and descriptive problem statement. I couldn't open the code b/c I'm still on LV 2016, but the screencaps show me quite a bit.
You're experiencing classic issues related to dataflow and race conditions.
The tab control, starting with page 2 active:
This is a dataflow issue. The tab control value is read instantly on startup. It "travels" by wire to the event structure boundary where it acts as an input. The event structure times out immediately, and the page 2 case will initialize your page 2 indicator(s). The While loop proceeds to the next iteration where you read the tab control value again. Only a millisec or two has probably passed, so the tab control still has the page 2 value which again travels by wire to the event structure.
Now the event structure has an infinite timeout, so it waits for one of its events to occur. When, after some time has passed, you change to page 1, the event structure wakes up and delivers its (now stale) tab control value of "page 2" from the tunnel to your case structure.
When you loop back around for your next iteration, the new value "page 1" will be delivered to the event structure. But the structure waits for a new event. If that event is you clicking to change over to page 2, then the (now stale) tab control value of "page 1" will initialize your page 1 indicators. But you just clicked over to page 2 so you don't see it happening. Not until you click back to page 1 again.
Control 1, needs multiple toggles:
This is a race condition. In parallel, you are writing a value to Control 1 (via property node "value") while also reading one (directly via terminal). There's no predicting which will happen first nor any guarantee that it will *always* keep happening first. You were both lucky and unlucky. Unlucky because the "wrong" thing happened first for you. Lucky because it helped you find this bug now instead of having things work correctly by coincidence, only to come back and bite you later.
Short Term Fixes:
Move the Tab Control terminal inside the event case. Then it won't be read until *after* a gui event fires and its value won't be stale. (Probably. See more below.)
I think the shift register that's used to write back into Control 1 is a case of wrong thinking. I don't know what you *wanted* it to do, but I think you're undermining yourself with it.
Get rid of them and simply initialize Controls 1 & 2 once before entering the loop. Be sure to use dataflow sequencing to make sure the initialization must happen before the loop is able to start. (Example: write to their value property nodes, and wire the nodes' error output to the loop boundary.)
Longer Term ideas:
I'm a big proponent of using the event structure's "OldVal" and "NewVal" properties as *part* of the strategy to manage these sequencing issues. However, that usually also means having a distinct event case for each GUI control. And then the kind of thing you're doing (update on any 1 of several possible GUI events) takes a little more work.
The problem with reading from a local variable or terminal is that it's *possible* to retrieve the wrong value if a bunch of events got queued up as a backlog. It could be a problem of a "not stale *enough*" value. (Event backlogs should be rare if you use good programming practices with your event cases. Namely, no event case should require any appreciable time to execute completely.)
Example: suppose you have a boolean GUI switch. It's normally F for idle state but when you toggle to T it means you've switched to active run state. Now further suppose that you had an event case that got stuck for 10 seconds. During the 10 seconds that case is stuck executing, any new events will get stuck in a backlogged queue. So let's even further suppose that within that 10 sec stuck time, you toggle your switch to T for a few seconds and then back to F. Two value change events go into the queue backlog. When the 10 sec event case finishes, you'll finally get to process these new events for your GUI switch. But if you read the value of the GUI switch from its terminal or a local variable, you'll process 2 events and both times you'll read a F value from the switch. You're reading the value "right now" instead of the value that was true "at the moment the event occurred". The value literally isn't stale enough. Querying the OldVal and NewVal properties would (likely) be better because they would carry values that were true at the moment of the event.
Class dismissed.
-Kevin P
05-22-2019 07:41 AM - edited 05-22-2019 07:42 AM
Thanks Altenbach, I'm not sure what you mean by "putting indicators before the SR" (SR = shift register?).
Would I then use a property node or local variable inside the event structure when I wanted the indicator to show the control's value?
The thing I find most odd about my VI is that Control 1 and Indicator 1 can show different states. Surely if they are directly wired together they should always be the same?
Roger
05-22-2019 07:56 AM
@johntrich1971 wrote:
Your problem was with data flow. You had no guarantees as to whether the control would be read first or written to first. I moved the initializers outside the loop. Another way to do this would be to make your initialization state separate from the other states. You could then set your values inside the initialization state as you had been doing and never enter that state again.
Thanks. I've now tried making the initialisation separate and it works with my complete VI too.
For someone "learning as they go along", the data flow question seems to introduce all kinds of reliability issues (I'm really glad my VI will not be running a nuclear power station). I would be happier if LabView had some way of dictating the data flow path.
05-22-2019 08:03 AM
Generally if you need to dictate the data flow path you do so with wires. Make sub-vis if necessary and use error wires to dictate flow. As you use LabVIEW more you will see advantages as well.
05-22-2019 08:32 AM
@Kevin_Price wrote:
[The perils of writing a reply a minute here, a minute there. Other help showed up before I finished. It's a good summary, but I'll leave my stuff below because it explains the details more.]
Thanks for such a clear and descriptive problem statement. I couldn't open the code b/c I'm still on LV 2016, but the screencaps show me quite a bit.
You're experiencing classic issues related to dataflow and race conditions.
The tab control, starting with page 2 active:
This is a dataflow issue. The tab control value is read instantly on startup. It "travels" by wire to the event structure boundary where it acts as an input. The event structure times out immediately, and the page 2 case will initialize your page 2 indicator(s). The While loop proceeds to the next iteration where you read the tab control value again. Only a millisec or two has probably passed, so the tab control still has the page 2 value which again travels by wire to the event structure.
Now the event structure has an infinite timeout, so it waits for one of its events to occur. When, after some time has passed, you change to page 1, the event structure wakes up and delivers its (now stale) tab control value of "page 2" from the tunnel to your case structure.
When you loop back around for your next iteration, the new value "page 1" will be delivered to the event structure. But the structure waits for a new event. If that event is you clicking to change over to page 2, then the (now stale) tab control value of "page 1" will initialize your page 1 indicators. But you just clicked over to page 2 so you don't see it happening. Not until you click back to page 1 again.
Control 1, needs multiple toggles:
This is a race condition. In parallel, you are writing a value to Control 1 (via property node "value") while also reading one (directly via terminal). There's no predicting which will happen first nor any guarantee that it will *always* keep happening first. You were both lucky and unlucky. Unlucky because the "wrong" thing happened first for you. Lucky because it helped you find this bug now instead of having things work correctly by coincidence, only to come back and bite you later.
Short Term Fixes:
Move the Tab Control terminal inside the event case. Then it won't be read until *after* a gui event fires and its value won't be stale. (Probably. See more below.)
I think the shift register that's used to write back into Control 1 is a case of wrong thinking. I don't know what you *wanted* it to do, but I think you're undermining yourself with it.
Get rid of them and simply initialize Controls 1 & 2 once before entering the loop. Be sure to use dataflow sequencing to make sure the initialization must happen before the loop is able to start. (Example: write to their value property nodes, and wire the nodes' error output to the loop boundary.)
Longer Term ideas:
I'm a big proponent of using the event structure's "OldVal" and "NewVal" properties as *part* of the strategy to manage these sequencing issues. However, that usually also means having a distinct event case for each GUI control. And then the kind of thing you're doing (update on any 1 of several possible GUI events) takes a little more work.
The problem with reading from a local variable or terminal is that it's *possible* to retrieve the wrong value if a bunch of events got queued up as a backlog. It could be a problem of a "not stale *enough*" value. (Event backlogs should be rare if you use good programming practices with your event cases. Namely, no event case should require any appreciable time to execute completely.)
Example: suppose you have a boolean GUI switch. It's normally F for idle state but when you toggle to T it means you've switched to active run state. Now further suppose that you had an event case that got stuck for 10 seconds. During the 10 seconds that case is stuck executing, any new events will get stuck in a backlogged queue. So let's even further suppose that within that 10 sec stuck time, you toggle your switch to T for a few seconds and then back to F. Two value change events go into the queue backlog. When the 10 sec event case finishes, you'll finally get to process these new events for your GUI switch. But if you read the value of the GUI switch from its terminal or a local variable, you'll process 2 events and both times you'll read a F value from the switch. You're reading the value "right now" instead of the value that was true "at the moment the event occurred". The value literally isn't stale enough. Querying the OldVal and NewVal properties would (likely) be better because they would carry values that were true at the moment of the event.
Class dismissed.
-Kevin P
Hi Kevin
Thanks for such a detailed reply: I think I see what's happening now. I've put my initialisation code outside the loop and it seems to work.
The reason for the shift register approach was that I was trying to have just one copy of my code that populates the GUI. I now have one copy of the initialisation code before the loop and another in the "Read" event case - I never like having two copies of the same bit of code but don't know enough LabView to use a subVI for this (the code sets the string list for Ring controls - I can pass the Ring values in and out of a VI but not the Strings). It's not a huge bit of code to duplicate though.
For interest, the VI will work with the cDAQ chassis and a number of C-series modules.
The initialisation sets the Voltage/Pressure/Flow and the cDAQ1Mod1/cDAQ1Mod2/... strings (and their equivalents for the thermocouple, RTD etc modules, using the same TypeDef for the clusters; the thermocouples will show types J/K/T as the ring strings). The configuration file will add previously-saved transducer names etc. The value change event counts the number of active channels and assigns the column numbers that will be used when it writes the acquired data to a Matlab file.
- Roger
05-22-2019 11:35 AM
@roger1922 wrote:
@johntrich1971 wrote:
Your problem was with data flow. You had no guarantees as to whether the control would be read first or written to first. I moved the initializers outside the loop. Another way to do this would be to make your initialization state separate from the other states. You could then set your values inside the initialization state as you had been doing and never enter that state again.
Thanks. I've now tried making the initialisation separate and it works with my complete VI too.
For someone "learning as they go along", the data flow question seems to introduce all kinds of reliability issues (I'm really glad my VI will not be running a nuclear power station). I would be happier if LabView had some way of dictating the data flow path.
LabVIEW's greatest advantage is that the only thing that constrains it is YOU. I know it's a scary thought to have so much control over your code. The first step is to get over it; the next step is to embrace it.
Drill this one concept into your head:
"Remember that a node executes only when data is available at all of its input terminals and supplies data to the output terminals only when the node finishes execution." (A structure qualifies as a node.)
By ruthlessly applying this one sentence to your code, you can predict how your code will run. And if you can't, then you have a race condition.