My proficiency with concurrent processing is pretty low. (**)
I got bit a few times recently with the sequence of triggering Perform Actions, and I would like to confirm that the way I think it works is correct and will always continue to work, in any runtime environment.
Here is what I found that does not work:
Do Stuff A
Perform B
Do stuff C that depends on B having been completed.
I was thinking of a Perform like a Subroutine / Subprogram. Bad news.
Now I understand that B is queued for later execution, which happens after C. So I changed it to:
Do Stuff A
Perform B
Perform C
… and created a new Perform C translator for all the C stuff that depends on B having been completed. However, my question is:
Will the Perform C always execute after the Perform B? … or is there some multi-threaded situation where Perform C could precede Perform B?
Even worse, If B does a Perform B2, will that execute after the Perform C?
(**) Worked at NYU on some of the core multi-threading code on the first validated Ada compiler (written in an amazing set-based language, appropriately called SETL). Despite that, I’m constantly blindsided when I actually use concurrency … If I were a Dining Philosopher, I’d starve.
Unlike function calls perform doesn’t return anything so you would need to store the return state in a global variable.
The below example might be what you are looking for. I have a Main perform ‘Dispatch’ that calls Perform A, B and C respectively. Perform A is called with no delay and B and C with 100ms delay. The main program also calls itself so that it is essentially polling until all actions are completed. I use the global variable ga to track the return status (whether it has been completed or not).
When the last return has been completed, the value of ga will be >2 and the recurring perform ‘Dispatch’ will stop.
A word of caution, since Dispatch is a recurring perform, it is advisable to have a way to make it stop to prevent consistent spinning.
For this reason I have a translator (0.5) that when triggered, sets the value to 100 so the perform stops on the next iteration.
I added log messages within the rules of the translators so you can watch the action in the log window.
So this situation happens a lot in the .BMTP for my current rig, and the machinery of the timers would be a significant amount of machinery to install.
I am hoping that what I’m currently seeing … that when I have in my rules:
Perform B
Perform C
Whether it is guaranteed that the actions in C will always happen after the actions in B.
… If that is not the case, then I’ve probably got some ‘subroutine unraveling’ to do (rather than timers set in a Dispatch).
Translators can happen in parallel and are not dependent on the previous translator from completing. They are designed to do that for performance reasons. Without using something like my example, there is no guarantee of completion status or the order in which they complete.
My example didn’t use timers but rather a recurring perform translator and a global variable to ensure the previous perform translator completed before triggering the next one.
Also in my example, the status (value of the global variable) just indicates the perform action completed and not any king of “return” status to the calling translator.
Sorry!
Steve Caldwell
Bome Customer Care
Also available for paid consulting services: bome@sniz.biz
Hi,
interesting discussion! Steve’s replies are correct, however, there is a trick to force serialization of events.
But first, I’d like to make sure you understand how concurrency can happen. The MIDI processing engine receives events from a variety of sources, e.g. MIDI devices, keystrokes, etc. These incoming events can happen concurrently, i.e. a MIDI message from device A can be processed at the same time (literally) as an incoming MIDI message from device B. The incoming and Rules sections of such translators will be executed in parallel, too. Now for the outgoing action, it depends on a number of factors when it will get executed:
some outgoing actions are executed ‘in place’, i.e. in the same context as the incoming action. For example, sending MIDI. In the example above, if both translators from device A and device B send MIDI as outgoing action, they will send their messages concurrently.
other outgoing actions are forced to be placed on an asynchronous queue (‘dispatcher’). This is is true for all outgoing actions that are ‘slow’ like keystroke emulation (to not hold up event processing of incoming actions).
specifying a delay for an outgoing action will also put the outgoing action on the dispatcher queue.
The dispatcher works in parallel to incoming event processing, but for events of the same type, they are guaranteed to be executed by the dispatcher in order received and one after another.
So a trick to force outgoing actions to be put on the dispatcher queue is to specify a delay of 1 millisecond. In most cases, a delay of 1ms is not noticeable, but it will ensure that Perform B is fully processed before Perform C is executed.
When using Perform as outgoing action, this trick will only ensure that the incoming Perform event (and associated Rules) will be executed before Perform C is executed. If a matching translator’s outgoing action is sent to the dispatcher (according to the rules above), it will be executed after Perform C is executed.
I hope that makes sense! A diagram would be nice, but hard to get right, I guess.
Not that I’m aware of. This is primarily why I have a dispatcher perform translator, so that it can take the incoming delay as a parameter from the dispatcher and use it as a delay for the outgoing action.
Steve Caldwell
Bome Customer Care
Also available for paid consulting services: bome@sniz.biz