When developing apps with multiple parallel processes, many developers prefer messaging systems over reference-based data. One of the difficulties in messaging applications is how to handle request-reply sequences between two loops. Broadly speaking there are two approaches: synchronous messages and asynchronous messages.
In some ways synchronous messaging is easier to understand. The sender asks the receiver for a piece of data and waits for a response before continuing. The downside is the sender isn't processing new messages while its waiting. Important messages will go unnoticed until the sender receives the data or gets tired of waiting. It can also be hard to verify correctness in synchronous messaging system. Cross loop execution dependency may hide deadlocks in your code and they are very hard to identify.
Asynchronous messaging sidesteps the issue of deadlocks. Each loop executes on its own schedule so there's no danger of multiple loops waiting on each other. The drawback? There are a lot more messages to handle and the bookkeeping for sequences of data exchanges can get messy. For example, if a data consumer, C, needs data from a data producer, P, it might send a GetData message then continue servicing messages as they arrive. When P processes the GetData message, it in turn sends a HereIsData message back to C. C then not only needs to implement a HereIsData message handler, but also needs to keep track of the reason why it wanted the data in the first place so it can continue its previous task--there is more state to manage. Since synchronous messaging processes the returned data inline with the request it doesn't have this problem.
Often I eliminate the need for additional state management by having P broadcast new data to all C's every time it changes. That ensures each C has the latest data and can use its internal copy inline instead of requesting it and waiting for a response. Sometimes that is not a very good solution. Consider the case of a high output P and a C that sometimes, but rarely, needs the data. That's exactly what I ran into recently with a motion control system where the motor positions are updated every 10 ms or so but the UI needs to know position data maybe 5 times every 30 minutes. Continuously broadcasting position data to the UI seemed like a waste of resources.
Where am I going with all this? A little while ago I ran across a concept called "futures." It is essentially a promise to supply needed data at some point in the future when it is not available at that instant. The future doesn't have the data now, but it will when it is redeemed. Rather than broadcasting thousands of unnecessary messages or creating lots of extra states to manage, I used futures to get the readability of synchronous messaging while (mostly) maintaining the natural parallelism of asynchronous messaging.
I don't have time to code up an example right now so let me try to describe it. (If there's interest I'll try to post an example later.)
The process flow I used for my futures is similar to this:
1. UI determines it needs position data from the motion controller (MC.)
2. UI creates a future and keeps one copy for itself, sending the other copy to the MC as asynchronous message data.
3. UI continues processing, not caring about the specific data right now but trusting it will be there when needed.
4. MC eventually processes the message and "fills" the future.
5. UI gets to the point in its execution where it needs the data, so it "redeems" its copy of the future and obtains the data filled by MC.
Compare that with synchronous messaging and the difference becomes clear:
1. UI determines it needs position data from the motion controller (MC.)
2. UI requests position data from MC.
3. UI sleeps while waiting for a response.
4. MC eventually process the message and sends a response.
5. UI continues processing, confident it has the data it needs for future processing.
I don't claim this idea as my own, or even that its new. I implemented the future using a notifier. In fact, under the hood a future looks a lot like a synchronous message. The important difference is where the waiting takes place. Synchronous messaging forces the sender to wait for a response to the message before continuing. Futures give the sender more control over their own execution. They can redeem the future immediately and behave like a synchronous message or they can redeem it in the future and continue doing other stuff.
It turns out I unknowingly implemented futures about a year ago as a way to have synchronous messaging with LapDog. At the time I was focused on obtaining the response before continuing, so it never occurred to me to defer reading the notifier until the data was actually used.
I've just started using this idea so I don't really know where it'll lead. I don't think it's a "safe" replacement to synchronous messaging; there is still the danger of deadlocks if futures are used extensively. I think they're better used as a lightweight request-response mechanism when implementing a new state is too heavy and broadcasting is too resource intensive.