Sorry, I've been really busy with the beta and haven't had time to devote to this until now, as it requires a rather lengthy explanation.
First, a little background:
With static event registration, an event queue is directly associated with the Event structure, and events are queued only when the front panel of the VI containing an Event structure is running (or reserved to be run by a top-level caller VI). This is nice and easy to use, but clearly limited to simple use cases and simple applications.
Dynamic events are intended extend LabVIEW event handling by giving the user complete control over the lifetime of event generation. You control exactly when your app starts to queue events, which events are generated, when the events are handled, and when to stop event generation. Since these are separate moments in the lifetime of an application, they need to be controlled by separate nodes. An application using dynamic events at various points may need to:
- Register for events to start them being enqueued..
- Handle currently registered events as they occur in one or more Event structures, which run subject to the dataflow requirements of the application (taking care that the Event structures run often enough not get backlogged so as to allow your app to be responsive).
- Optionally unregister for and reregister for different objects in your app as may be required to maintain application state or transition between different modes in the UI.
- Unregister for events when they are no longer needed.
To allow for a flexible system, I hope it is at least 'intuitive' to you that these operations need to be separate entities.
Now, in order for the system to be able to keep track of events as they happen from the moment they are registered, the event queue for dynamic events clearly must be associated with the Register for Events node. From the moment this node executes, the specified events must be enqueued by the system, and there is no way for the system to tell what Event structures will eventually run to actually handle those events and pull them from the queue. To do so would require static analysis of your entire VI Hierarchy, and even that wouldn't work in the face of dynamic calls, or the use globals or other asynchronous communication to pass the event registration refnum.
Therefore, the queue is associated with the event registration refnum produced by the Register for Events node, and normal LabVIEW dataflow is used to pass this queue reference to the Event structure downstream which will eventually process events from it. This is a natural way to handle events in a data flow language, and I think this should be pretty intuitive to LabVIEW users as well.
Events in each event queue are reference counted, and each event is guaranteed to be processed in the order delivered (per queue), and to be handled by at least one downstream Event structure (and in correct programs, exactly one.) When an Event structure starts to process an event from its wired dynamic queue, it increments the ref count. When the diagram finishes or the event is otherwise discarded, the ref count is decreased, and when it goes to zero the event is removed from the queue. If multiple Event structures happen to try to read from the same dynamic queue, more than one of them may get the event if they happen to execute while the first Event structure is still executing, so the event's ref count may go higher than 1. But the only guarantee is that it will go to 1 (to make sure at least one Event structure handles it), and then back to zero.
So the question is, why don't we allow an arbitrary number of Event structures to be
guaranteed to process dynamic events from the same dynamic queue. And the answer is that if we did, we'd have to know ahead of time how many there are, and as I stated above, we can't do this because we have no way to analyze where data flow might take the dynamic event queue refnum without actually executing the code.
(Note to computer scientists out there: this is equivalent to solving the famous Halting Problem, and is NP-complete)If an Event structure receives an event via its dynamic event queue that it doesn't have a handling diagram for, it silently discards the event. You could certainly argue that we should trigger some sort of detectable error when this happens (via a 'default' event case or error out terminal, for example, and we have discussed this and it may show up in a future release). However, you cannot reasonably argue that we should leave the event on the event queue if it is not handled, because that would cause problems in many more situations than the current behavior. The common, correct use case is to have one Event structure per dynamic event registration, and so if we left the event in the queue if unhandled, expecting it to be handled by another Event structure, then the queue would be blocked indefinitely if there was no one else to handle the event, which is most of the time.
Your idea of having the Event structures own the dynamic queues would not work for several reasons. Event structures can be anywhere in the VI hierarchy (or not even loaded yet if called dynamically). There's no guarantee a given Event structure will even run, and there would be no way to know which Event structures the user intends to handle events for which VIs, controls, user events, etc. So LabVIEW would have to put a copy of each 'enabled' event in each queue for all Event structures currently loaded, and then decide only when those Event structures actually run if the events really belonged in that queue based on which dynamic reference value they got. Event structures which never run would have their queues grow without bound. And Event structures in dynamically loaded VIs would miss the events which happened before they were loaded, even though the events were registered for. Further, if every Event structure had a separate queue
per event, it would be very hard to maintain the rule that events are handled by the Event structure in the order they occur. It's not hard to see that this is an unworkable idea.
So to answer your tangential questions:
Yes, the main reason events are not removed from the queue until the event handling diagram completes is because of filter events. The event queue actually stores event information from the OS which is not visible on the diagram, such as the original raw system event data, the system window handle the event was on, etc. This information must be maintained and reprocessed when the filter event handler completes, and if a filter event is registered by multiple observers, must be redispatched to the next event handler until all register handlers have executed (or the event is discarded).
And yes, we still do not consider your original issue a bug. The behavior of event handlers when used incorrectly in this way is by definition undefined, and it's undefined for a good reason. The extra bookkeeping, mutexes, etc. to make multiple Event structures reading from the same event queue work as you expect would add overhead to the whole event mechanism. There is a trade off here, and we simply don't consider it worth it to complicate the entire event architecture to make 'bad' code work in predictable ways at the expense of making 'good' code slower.
We may consider adding diagram warnings for the cases where we can detect you are branching an event registration refnum. However, it's not possible to prevent it completely; you could, for example, write the registration to a global and then read it from multiple unrelated subVIs.
In your example, because the bottom loop's Event structure doesn't use any of the dynamic events the event registration, it would indeed be possible for us to pretend the refnum wasn't even wired in this case and not try to read events from this queue. But there's no reason for us to do so because it would do nothing to solve the more general problem. You can populate this Event structure with diagram handlers for any subset of the registered events and the same behavior will occur.
We do take care to try to ensure that even in cases of 'bad code', we always maintain data integrity, do not leak memory, and do not put any VI in a state where it cannot be aborted via the stop button on the panel. But the current "undefined behavior" when you try to have multiple Event structures read from one dynamic queue is part of the design, is intentional, and is a reasonable trade off to make the common, correctly written case work efficiently. We could do more to make things work consistently in these cases, but it would slow down the common case, and it still would not be possible to eliminate the fundamental race condition that exists because it is not possible in general to know how many Event structures might handle the event.
--Craig
Message Edited by Craig S. on 05-11-2007 04:27 PM