Jump to content

How to open front panel after N milliseconds unless the operation is done


Recommended Posts

Someone at work was building a dialog for a load progress bar. If the load process took more than N milliseconds, the panel should display. If the load process finished first, the panel should not open. Here's what I wrote for him. Anyone have any improvements to suggest?

[LATER EDIT] Thanks to mje who posted a fully correct solution with all the comments from this thread. You can find the correct solution here.

post-5877-0-21875700-1317418800.png

  • Like 1
Link to comment

Anyone have any improvements to suggest?

Uhh... straighten out your error wire? (j/k ;) ) Clever. I can't help but think about ways of generalizing it into a sub vi that can execute an arbitrary process.

Hmm... My first inclination would be to use an occurrence instead, but that doesn't change the functionality. Tim

Hasn't NI deprecated occurrences in favor of notifiers?

Link to comment
Hasn't NI deprecated occurrences in favor of notifiers?

I hadn't heard that, and would actually find it surprising. Though I haven't used them very many times, I go to occurrences first when I need really simple signalling. DAQ uses them too, doesn't it?

Also, rather than the fill-in-the-blank approach, I would have the VI accept a VI reference and use a blocking Run call. It would force you to make all of the potentially-slow code a subVI, but that may not really be a bad thing. On second thought, though, if the potentially-slow code needed any local data (or returned anything), it would take a more work to pull that off.

Link to comment
Also, rather than the fill-in-the-blank approach, I would have the VI accept a VI reference and use a blocking Run call. It would force you to make all of the potentially-slow code a subVI, but that may not really be a bad thing. On second thought, though, if the potentially-slow code needed any local data (or returned anything), it would take a more work to pull that off.

One could make two subVI's, "Open FP after delay" that outputs the notifier, and "Close FP" that inputs the notifier (as well as the error out of the rest of the code). Then it would be easy to add to any VI and be quite clear. Internals of the two VIs would need to be more complex, with an async call of a third subVI with the actual wait on notification, and some method of making sure "Open..." is finished before "Close..." actually closes the front panel.

-- James

[i'd try and write it if the graphics card on my work laptop hadn't up and died two days ago! "Seven working days to fix" :angry:]

Link to comment

For kicks and grins I whipped up a quick example of a reusable and semi-generic process progress bar using (of course) LVOOP. I wouldn't consider this adequate for a reuse library since it is UI-centric, but it is something I might use as reusable code within an application. Code is in LV2009.

Example Code BD

post-7603-0-00955200-1317494124.png

ProcessProgressBar.DoProcess BD

post-7603-0-54360500-1317494032_thumb.pn

WaitProcess.ProcessRunner BD

post-7603-0-82048500-1317494076_thumb.pn

I hadn't heard that, and would actually find it surprising.

FYI... from LV2009 help, Generate Occurrence Function:

Note National Instruments encourages you to use the Notifier Operations functions in place of occurrences for most operations.

Whether or not that means they deprecated occurrences is subject to interpretation I guess.

ProcessProgressBar.zip

Link to comment

FYI... from LV2009 help, Generate Occurrence Function:

Note National Instruments encourages you to use the Notifier Operations functions in place of occurrences for most operations.

Whether or not that means they deprecated occurrences is subject to interpretation I guess.

Thanks for the source. I'd like to hear more about this, not because I love occurrences so much as I'm curious why the recommendation arose. The wiki article does mention some "unexpected" behavior - maybe that's it.

Link to comment

Let me fix that for you...

> I'd hazard a guess that notifiers occurrences aren't recommended due to their behavior being

> different than the other sync objects. The behavior is a little odd, but once understood they are perfectly usable.

With my amendments, yes, you're correct

It is very hard for most folks I've worked with to understand how and why occurrences work. The Notifier wrapper (it uses occurrences under the hood, as does all sleep/wake behavior of LV) generally makes things clearer.

I'd probably throw in a release primitive in there for good practice, but otherwise all good I'd say.

Yeah, that should be added since he's probably calling this as a subVI repeatedly.

Link to comment

It is very hard for most folks I've worked with to understand how and why occurrences work. The Notifier wrapper (it uses occurrences under the hood, as does all sleep/wake behavior of LV) generally makes things clearer.

The amusing part of that is I'm the opposite... I understand occurrences better than notifiers. Though, in full disclosure, I can't ever recall using notifiers.

Tim

  • Like 1
Link to comment

Occurrences are the underlaying functionality of all LabVIEW asynchronous operations. However they have a few limitations in itself.

1) They don't allow for any data to be associated to them

2) They have somewhat confusing semantics

3) You can get a triggered occurrence from set occurrence invocations that happened before the wait occurrence was called even when ignore previous is true. This can be worked around easily by checking for the actual event in some other ways and reentering a wait if the event wasn't really true, but one needs to be aware of it.

  • Like 1
Link to comment

I had a conversation with our chief architect whose been working on LV since version 0.1 and who created the occurrences long long ago. Here is THE correct way to use occurrences:

First build a system which polls busily for whatever it is the occurrence will signal. You need to be able to determine the state of things without an occurrence. This creates a correct, but inefficient busy waiting implementation. Once that works, add an occurrence to alleviate the inefficient waiting. Do not remove the actual polling. In other words, use the occurrence only to indicate that "it is probably a good time to check on that thing...".

[Later Edit] I added a much better example of the usage of occurrences a few days later in this thread. Read it here.

In other words, start with this:

post-5877-0-78906900-1318019474.png

and then go to this:

post-5877-0-31239600-1318019479.png

As I understand it, occurrences don't have thread safety on their state because they are the building blocks by which thread safety for state in higher level APIs is built.

  • Like 1
Link to comment

I had a conversation with our chief architect whose been working on LV since version 0.1 and who created the occurrences long long ago. Here is THE correct way to use occurrences:

First build a system which polls busily for whatever it is the occurrence will signal. You need to be able to determine the state of things without an occurrence. This creates a correct, but inefficient busy waiting implementation. Once that works, add an occurrence to alleviate the inefficient waiting. Do not remove the actual polling. In other words, use the occurrence only to indicate that "it is probably a good time to check on that thing...".

In other words, start with this:

post-5877-0-78906900-1318019474.png

and then go to this:

post-5877-0-31239600-1318019479.png

As I understand it, occurrences don't have thread safety on their state because they are the building blocks by which thread safety for state in higher level APIs is built.

That won't work.

  • Like 1
Link to comment

Care to elaborate? (I've never used occurences and I'm finding it a bit confusing.)

Because the local variable will be evaluated well before the first loop reaches the large number and will present a FALSE to the stop terminal. Everything (in the second loop) then waits for the occurance. Once Loop 1 actually fires; loop 2 then proceeds with the FALSE, goes around again and then waits, once more, on the occurance-which never arrives since the first loop has already terminated.

This is why no-one uses them. Its too easy to get race conditions that hang your app because they don't have a time-out.

It will work correctly IF you put the occurance and the local into a sequence structure that guarantees the local is read AFTER the occurance (another reason no-one uses them since they don't have error terminals and forces you to use those pesky sequence structures.)

Edited by ShaunR
  • Like 1
Link to comment

Occurrences do have a timeout.

The main reason occurrences behave differently than the other sync objects is because occurrence refnums are static. The refnum is created when the VI that contains the Generate Occurence function is loaded, not when it is executed. That means that a VI which is reused, say in a loop, the Generate primitive will return the same occurrence on each invocation. This is often counter-intuitive to those that don't understand the mechanics of how occurrences are created and can lead to some serious bugs due to previous values being in the occurrence.

The fact that they're created on load is also the reason there is no need for a destroy primitive. Memory leaks don't happen because repeated calls to Generate Occurrence in the same VI always return a reference to the same instance.

The behavior is very useful if the primitive behaviors are understood though. They just work completely different from all the other synchronization primitives.

Link to comment

Occurrences do have a timeout.

I stand correct4ed (when did they add that then?)

The main reason occurrences behave differently than the other sync objects is because occurrence refnums are static. The refnum is created when the VI that contains the Generate Occurence function is loaded, not when it is executed. That means that a VI which is reused, say in a loop, the Generate primitive will return the same occurrence on each invocation. This is often counter-intuitive to those that don't understand the mechanics of how occurrences are created and can lead to some serious bugs due to previous values being in the occurrence.

IMHO this is how the other primitives should behave (and that includes diagram, project and control refs). I've lost count of the number of times I have had to correct others' code because the refs aren't closed. It is these primitives that are counter and intuitive rather than the occurrence. Oh for the days when Labview meant you didn't have to worry about memory leaks.

The fact that they're created on load is also the reason there is no need for a destroy primitive. Memory leaks don't happen because repeated calls to Generate Occurrence in the same VI always return a reference to the same instance.

The behavior is very useful if the primitive behaviors are understood though. They just work completely different from all the other synchronization primitives.

Seems to me that they work similarly to a notifier (since they have a timeout) without the pit-fall of memory leaks.

Edited by ShaunR
Link to comment

This may be a stupid question--like I said I don't use them and I don't have LV in front of me atm to test it out...

I thought setting Ignore Previous to false would allow the second pass through loop 2 to execute normally, because it's not ignoring the occurrence that was previously generated. I take it when WoO detects an occurrence it effectively "clears" any pending occurrences?

Given WoO does have a timeout, the sample code will execute to completion, it'll just take one full timeout period longer than the time it takes to for the occurrence to fire. Is the timeout user configurable?

mje, do you have an example where an occurrence's static refnum "feature" is an advantage over a regular notifier? I'm having a hard time thinking of a use case.

Link to comment

This may be a stupid question--like I said I don't use them and I don't have LV in front of me atm to test it out...

I thought setting Ignore Previous to false would allow the second pass through loop 2 to execute normally, because it's not ignoring the occurrence that was previously generated. I take it when WoO detects an occurrence it effectively "clears" any pending occurrences?

Given WoO does have a timeout, the sample code will execute to completion, it'll just take one full timeout period longer than the time it takes to for the occurrence to fire. Is the timeout user configurable?

mje, do you have an example where an occurrence's static refnum "feature" is an advantage over a regular notifier? I'm having a hard time thinking of a use case.

Occurrences work a bit more complicated. I have to admit that my memory is a little foggy about the exact details of this, as it has been several years that I struggled with this for a rather complicated infrastructure, involving use of occurrences also in external C code.

But from what I remember, once an occurrence has triggered it is in the set state (to use a more common concept of ResetableEvent as analogy). When a WoO executes for the first time it looks at the state and when ignore previous is true it will USUALLY wait even when the occurence is currently in the triggered state. When it is false it will just continue when the occurrence is in the triggered state. In any case when WoO returns without timeout it will reset the state of the occurrence for this instance of WoO only. Next time around this WoO will use its local state to detect previous triggers when ignore previous is false. Other WoO instances will not influence the behaviour of this WoO in respect to detecting previous triggers. But a specific WoO can only wait once on a trigger eventhough you may have ignore previous set to false.

What I encountered sometimes was that the occurrence could get triggered the first round around even when ignore previous was true AND the trigger event had clearly occurred before the wait executed. My solution was to use the occurence as an indication that something might have happened and when WoO returned without timeout to actually check for the data to have arrived and if it didn't so far treat it as timeout anyhow, usually looping once more for an event.

This was back in LabVIEW 6 or 7 days and I have since always used occurrence in a way that was tolerant to this behavior. At first I considered it a bug of the occurrence but some discussions let me believe that it was an artifact of concurrent programming that can't be completely avoided and use of this should always be prepared for this possibility. NI might have changed something in the occurrence handling since and it may not behave exactly that way anymore, but I wouldn't really notice as my implementation is tolerant to the old behavior but of course won't break on an improved behavior of the occurrence.

As to a case where occurrences have an advantage over the other asynchronous objects in LabVIEW: If you want to be able to trigger an event from external C code the only native LabVIEW objects that are to my knownledge available for this are Occurrences since LabVIEW 3 or 4 and User Events since LabVIEW 7.1. None of this is well documented but there are for both semi offical NI examples floating around.

I stand correct4ed (when did they add that then?)

Wait on Occurrence had that as long as I can remember. (But my long term memory is sometimes a bit fuzzy so it may not have been there in LabVIEW 3. It's definitely there as far back as LabVIEW 5.1, I just checked. :D )

IMHO this is how the other primitives should behave (and that includes diagram, project and control refs). I've lost count of the number of times I have had to correct others' code because the refs aren't closed. It is these primitives that are counter and intuitive rather than the occurrence. Oh for the days when Labview meant you didn't have to worry about memory leaks.

Using (undocumented) LabVIEW manager calls, one can create dynamic occurrence refnums. I have done so in the past for a data logging application, giving every single channel tag its own occurrence so clients could wait on value changes easily. Worked like a charm even for several 100 channels.

All the LabVIEW refnums created (except those created by the LabVIEW manager calls directly unless you use some other manager calls to register those refnums for automatic cleanup) are suspectible to automatic cleanup on termination of the hierarchy that created them. So they do get cleaned up eventually. Static refnums vs. dynamic refnums has always pros and cons on both sides. Try to execute Create Occurrence in a loop to create several occurrences for a varying number of objects. It won't work as expected. So while dynamic refnums do require extra care from a programmer to properly cleanup after use, they also offer much more flexibility. For the same reasons you could vote that string constants should be static to avoid a LabVIEW user creating memory hogs by wiring it to an autoindexing loop boundary of an infinite loop. But that would possibly render LabVIEW even turing incomplete as you could not deal with a lot of common situations anymore.

Seems to me that they work similarly to a notifier (since they have a timeout) without the pit-fall of memory leaks.

Yes notifiers and occurrences are similar but a notifier can have a data item attached to it, while an occurrence only carries the notification event itself.

  • Like 2
Link to comment

This has been interesting to follow. The only thing I think I can contribute at the moment is a "typical" usage I would have.

post-4230-0-28362400-1318095561.png

The advantage I see with this is that I don't have a data type to go with the occurrence nor is it necessary.

Tim

Link to comment

Occurrences work a bit more complicated...

Thanks Rolf. As always, your knowledge of the lesser known aspects of Labview leave me slack-jawed. (I had never heard of "Labview Manager" and had to Google it.)

Here's the "Continuously Generate Occurrences" example that ships with 2009.

post-7603-0-13958600-1318099361_thumb.pn

Every 10th iteration of loop A generates an occurrence and causes loop B to execute. I thought it was interesting to see a Set Occurrence after loop A exits. I understand why it's needed, it's just not the first route I would have tried. (But as you can see from my earlier posts I have very little understanding of this construct.) This causes loop B to get an extra iteration when you exit loop A.

I started wondering how to prevent the extra occurrence, so I added a timeout and removed the tailing Set Occurrence.

post-7603-0-88927200-1318100654_thumb.pn

On the surface it seems like that should work, but there's a subtle race condition. Because the WoO has a timeout it is possible for loop B to read the stop button, exit, and reset before loop A has an opportunity to read it. With an infinite timeout that will never happen. I suppose you could put the button reset in a sequence structure and wire outputs from both loop A and B to it, but that feels kind of klunky to me. The other alternative is to set the timeout longer than the time between occurrences, so it will only timeout once the occurrences have stopped being generated. That doesn't seem like a very robust solution.

I guess I'm left thinking they're okay for simple cases--such as Tim posted--where the occurrence will only fire once telling the receiving loop to stop. But at the same time I can't think of any reason (outside of interacting with external code) I'd want to bother with them. Notifiers and queues have the same functionality, provide more options for expansion, and the overhead is insignificant.

  • Like 1
Link to comment

On the surface it seems like that should work, but there's a subtle race condition. Because the WoO has a timeout it is possible for loop B to read the stop button, exit, and reset before loop A has an opportunity to read it. With an infinite timeout that will never happen. I suppose you could put the button reset in a sequence structure and wire outputs from both loop A and B to it, but that feels kind of klunky to me. The other alternative is to set the timeout longer than the time between occurrences, so it will only timeout once the occurrences have stopped being generated. That doesn't seem like a very robust solution.

I'm glad you explored that a little bit - my knee-jerk reaction was just to use the timeout in Loop B and all would be well. However, I propose another solution to the race condition: don't reset the local after Loop B completes, reset it before passing the occurrence into Loops A and B at the start. It leaves the boolean in a TRUE state, but I don't see that causing anything but aesthetic problems.

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.