July 15th, 2013, 12:03 PM
Asynchronous Programming: Connecting Response to Request
1. Client sends Request A to Server
2. Server begins processing Request A
3. Client sends Request B to Server
4. Server begins processing Request B
5. Server responds with results of Request B
6. Server responds with results of Request A
Is there a common strategy to telling the client which response is for which request?
For example, should the client include a unique ID with the request that's returned with the response?
It'd be nice if the server itself could provide the ID immediately after the request is submitted, but in a completely asynchronous/multi-threaded setup, I'm not sure I could guarantee that another response couldn't slip in first.
Also, if anyone knows of any good examples/information sites about asynchronous programming, I'd appreciate getting those.
July 15th, 2013, 09:59 PM
Your basic idea is correct about needing an indicator. The client has to provide this, because there is no guarantee that the server even received the request, much less can respond with what it thinks is an appropriate code. So the client sends a request that is tagged with an ID the client is keeping track of -- the server doesn't care what this is, it just knows it has to identify the related response with the same ID. The server can either respond with an acknowledgment ("I'm working on ID:foo") and the client can take an action based on that (shuffle its queue, start a different timeout counter or whatever), or the server can just be quiet and the client can go by timeout. Both approaches have complications when it comes to request queue management.
Books on this? Hrm... I've never read a book on it. I've learned mostly through experimentation and directly from people working on the same projects who had more experience in this area than me. One interesting place to learn about asynchronous programming in highly unstable environments is the Erlang community -- but that language/community certainly isn't for everyone.
Anyway, the basic rule is to treat every message as its own independent event -- so you must write everything so that it can work in a complete absence of any other context than the current message at hand, and make the assumption that message order will always be random and that a random % of messages will always be dropped. Its usually easier if you go ahead and proactively define a criteria for dropping your own messages (over the timelimit/count/whatever -- it no longer is valid, etc) to force yourself into a mental mode where you think through drop/mix/mangle situations thoroughly.
July 15th, 2013, 11:33 PM
Perfect. Thanks. I like the idea of the client handling it, too. That saves the first step of making the client aware of whatever ID the server came up with.
I just needed a little reassurance that I was heading down the right path.
July 15th, 2013, 11:55 PM
Just remember that some cases require the server to initiate the contact (any global event, for example) and in that case the server is the one that produces the ID if it is important that the client acknowledge or return anything pertaining to the message. So the more general idea is that the initiator needs to label and keep track of its own task queue, because there is no guarantee that the other side received the message to begin with, nor that any receipt/ack message will make its way back to the initiator.
Whether the initiator is the client or the server, or even defining which is the client or server can get pretty fuzzy in a hurry, so I often prefer not to discuss things in those terms. The terms you use will shape the way you think about things -- so being picky about semantics is a good thing (there is an even more general lesson here about life and politics...).
July 16th, 2013, 11:03 AM
Ah, I gotcha.
I figured that the server might provide updates on its own initiative but not that it might expect a response from the client. I don't expect my current app to work in that direction, but it'd be a good practice nonetheless.
July 17th, 2013, 07:54 PM
Speaking of client/server updates...
Depending on what you're doing its not always important that either side confirms every message, and unacknowledged one-way streaming is fine in a lot of cases. Sending live video stream data, for example... missing a chunk here and there isn't important, what is important is that the client can still prove its listening within its "I'm alive" threshold. Missing a coordinate or two in a stream of 40 location updates per second in an FPS or MMORPG isn't important, and this implies that the movement/combat/whatever system shouldn't have a heart attack if a few location updates are missed or received late/out of order. etc.
A lot of time in asynch literature is spent on finding ways to guarantee that independent system can deal with remote failures and still pick up where they left off or otherwise globally "not miss anything". It is easy to miss the benefit of determining early on what is trivial enough to not worry with, though. Its a lot like deciding when to use TCP vs UDP.
I like to imagine hard and squishy parts of systems. The hard parts can't miss anything and need transaction-style guarantees. I strongly prefer to code such systems in a functional style using functional languages that make it glaringly obvious that we're mapping inputs to outputs on a per-transaction basis. The squishy parts can miss stuff and can maintain temporary self-consistency on their own. I strongly prefer to code such inherently stateful systems as (sometimes ephemeral) simulations using an object-oriented style.
July 18th, 2013, 04:02 PM
The majority of my use-cases now are for requests that expect a specific response, so connecting the two will be important. And if I have a ping function of some sort, that'd of course connect the ping with the pong. But yeah, there are definitely situations that wouldn't need it. I just haven't gotten to any of them yet.
July 24th, 2013, 09:57 PM
"Asynchronous programming" is a bit of a misnomer, particularly in this context. What you are really looking for is advice on distributed systems; and I think that if you do some quick searches on that, you'll find a wealth of books and advice. Another aspect of this are the well known differences between sequential and event driven designs.
When I think of asynchronous programming, I tend to think of what I must do when I write interrupt handlers (event driven) as distinct from the imperative sequential logic (aka stream of consciousness style) that I might apply to solving most other problems in computing. But the differences are similar in some ways to those between distributed and non-distributed systems. Distributed systems need not be asynchronous, from the algorithmic perspective, but for practical reasons, they typically are.
Consider the sequential logic:
A = GetValue()
if ( A > 0 ) do something
else do somethingelse
If the GetValue function must wait for a device that operates asynchronously to this code, then it is possible the thread will stall until that value is made available. There is no way to know whether the data is already waiting to be read from a register or we will have to wait on a signal of some kind that allows us to return from the function. Even if we used an AsyncGetValue function in place of GetValue(), we must stall before we can test the result and decide which subsequent code path to take. This is where we get into the event driven programming paradigm and rewrite the algorytm in a more declarative format:
when A = 0 do something
when A != 0 do somethingelse
when Undefined(A) GetAnA()
Here we assume a lot of implied logic. But this is what you should be thinking about when you design a distributed system, particularly one that uses asynchronous communications to avoid stalls. Most of us tend to think in imperative-sequential mode and then we get into trouble when we discover the inherent design impedance that exists between the two modes.
Turn your imperative-sequential ideas into equivalent declarative statements. Then you can think about work queues that contain artifacts of as-yet unfinished business and maybe even write imperative-sequential code to process the queues. You should strive to completely understand your problem in declarative terms before you start writing code.
A good source of information on this topic can be found in the many design patterns books.
I no longer wish to be associated with this site.