Monday, June 02, 2008 11:46 PM
bart
Continuations, futures, and whatnot - Thoughts on some asynchronous patterns
This sounds like one of those posts that's about to draw scientifically sound conclusions. It isn't. The other day I had a discussion about various ways to express asynchronisity (or asynchronousness, whatever spell checker upsetting word you prefer) in API designs. The answer might be shocking, but it really depends and there are quite some promising evolutions around. I just want to give you a breakdown of a few possible designs, some sexier than others but that's just a matter of (good) taste. The moral of the story: asynchronous patterns (which become increasingly important in distributed systems programming) is a plural noun.
The dinosaur in the room: IAsyncResult
Nothing against those animals - we should appreciate the meaning giants give to life. Obviously, the asynchronous pattern realized by this giant is overly known by the average .NET developer. A fancy way of introducing it is by using a web proxy class generated by the tool of your choice (dinosaurs prefer wsdl.exe, modern age Earth citizens choose svcutil.exe) but ultimately it all boils down to what I'd call a scoped overlapped operation. It's scoped in a VB-style by means of Begin* and End* method pairs that denote the client-side view of the operation, while it's overlapped because obviously everything between Begin and End runs simultaneously with what's happening on the other side of the fence.
IAsyncResult just serves the purpose of a concert ticket: you get one when you enter, but you're not required to stay waiting for the concert to finish, you can just sneak in with the ticket to the after-party to get to hear how great it was (the result of the long-running operation). So do whatever you like in between. Obviously don't take this as my ultimate advise as the ideal way to 'attend' cultural adventures. The cool thing is you can keep waiting for one or more of those asynchronous operations to finish depending on how you want to proceed with the results. In the end, I haven't said anything more than WaitForSingleObject[Ex] and WaitForMultipleObjects[Ex] (or the Msg* equivalents in case you want to process messages during the background concert, contrary to regulations in most theatres today but highly recommended if you want to stay alert to stimuli from outer space while waiting).
Continuations
These beasts are more like boomerangs. Just throw them a message threaded to a boomerang and ultimately the answer comes back threaded on the boomerang. For the code-minded out there, here's a sample:
void ThrowIt(string message, Boomerang boomerang);
where the Boomerang class could be defined like
class Boomerang : Action<string> { ... }
Notice the return type of the ThrowIt operation: you don't get anything back. Well, that is, till you get the boomerang against the back of your head some time later when it returns. This just works fine, but thinking of a boomerang as the ultimate mechanism to perform asynchronous work is a little weird at first. In more theoretical terms, you're threading the future (callback, or continuation) to the current (the asynchronous call). Or think of it as the following transformation:
string result = ThrowAndWait("Hello World");
Console.WriteLine(result);
into
ThrowIt("Hello World", result => {
Console.WriteLine(result);
});
But what's more is the subtlety of the continuation itself, because of the whole plethora of closures implemented by the language. In boomerang terms, you're not just throwing the boomerang but all bacteria on your hand (the caller site environment) travel with it and might come back in some mutated form with the result. It's a bit different from the typical big red warnings trying to protect you from side-effects, since it's nicely disguised with a little "ought to be functional, yeah" lambda-arrow. Just try to reason about this one (and assume that you do know something or do not know anything about how ThrowIt operates):
string message = "Hello World";
ThrowIt(message, result => {
Console.WriteLine(result);
});
message = "Goodbye World";
There should be a side-effect no matter what: how on earth would a void-returning method do something useful otherwise? In fact, I bet your very first (procedural) program you ever wrote was one of your biggest lies:
static void Main() { ... }
Right, you've just declared something that heats your processor's transistors by passing in nothing and returning nothing. Obviously it get's a bit better when you return an int :-).
Futures
After the ancient dinosaur and the today's functional (?) programmer's wet continuation dream, let's switch gears to a more futuristic approach. No more boomerang-style rendez-vous anymore, what about just expressing we want an operation to be carried out, giving us back something somewhere in the future. Or how you go to a store to order something and get a voucher to come and get it when it's ready (or when you decide to just sit and wait because you can't live any longer without it). This is something that get's enabled by the Parallel Extensions to the .NET Framework's TPL library, so consider TPL-inspired syntax below:
Future<string> GetGreetingCard(string to);
which is a function that houses a script monk creating greeting cards. Let's assume our script monk is called String::Concat (not sure the first name is appropriate for a monk though) and submit a request:
var result = GetGreetingCard("World");
// do other shopping
Console.WriteLine(result.Value);
Actually, you get more control over it - you just pass the monk what you want to share but he doesn't get to see anything beyond that. And ultimately when you come back, there's a result that's predictable given what you gave to the monk in the first place (assuming deterministic monks, a rare sort those days). If you just want to sit and wait, you don't do any shopping between sending the request and getting the result back:
Console.WriteLine(GetGreetingCard("World").Value);
Actually, behind the scenes of the monastery there could well be laziness indeed - between receiving a request and carrying out the calligraphic job might well lie lots of secrets hidden behind thick walls. Without further elaboration one could smell the air of a thunk coming up or even SASL style of laziness.
Join patterns
If you've had a rendez-vous with Ada in a previous life, you'll be familiar with the concept of join patterns (or shorthand joins). Our Microsoft Research department has done quite some work on this field already, as you can read here: http://research.microsoft.com/~crusso/joins/. It's part of Polyphonic C#, which is by itself part of Cw and based on join calculus. We already got the LINQ inspiration from it, joins might be lurking around the corner too. There actually just two basic concepts to understand here:
- Asynchronous methods: imagine a keyword async as a modifier on methods (which would imply the method is a procedure, hence the void return type becoming redundant for those type of methods). No longer do you need to create separate versions of a method with or without the Async suffix to work around return type overloading limitations imposed by the runtime. You can think of the method as a wrapper around a task scheduling call, wrapping the entire body. Whether or not this spawns a new thread is another matter.
- Join patterns: also known as chords, is a declarative way of a WaitAll style of guard mechanism to enter a method.
Our monk would look like:
class Monk
{
public async RequestCard(string to) { ... }
public string GetCard() & public async RequestCard(string to) { ... }
}
Basically requests for cards queue up on the monk's GetCard operation, ready to get processed when the monk is awake. Actually this sample is convoluted and our monk suffers from some concurrency diseases (think of the cards it would hand out if there are multiple outstanding requests - any ordering guarantees without "ticketing"?) but I won't get in details for now - it's just (yet) another way of thinking about asychronous processing with today's materials in the room. If you're inspired by join patterns, check out the Polyphonic C# paper.
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks
Filed under: Comega, Functional programming