Option 2: Decouple "What" From "How"
Abstraction to the rescue:
- Use a separate general-purpose task launcher to launch the work. You probably have a number of options already available in your environment, such as being able to write
pool.run( /*task*/ )to run a task in a Java or .NET thread pool, orasync( /*task*/ )in C++0x. - Use futures to manage the asynchronous results. A "future" is an asynchronous value think of it as a "ticket redeemable for a value in the future." [2] This abstraction is available in Java as
Future<T>, in the upcoming C++0x standard asfuture<T>, and also as Task<T> in the next release of .NET.
For example, here is a simple synchronous call to CallSomeFunc:
// synchronous call (this will block) int result = CallSomeFunc(x,y,z); // … code here doesn't run until call completes and result is ready … // use result which is already available DoSomethingWith( result );
Here is a corresponding asynchronous call (a mix of C++0x and C# syntax):
// asynchronous call
future<int> result =
async( ()=<{ return CallSomeFunc(x,y,z); } );
// … code here runs concurrently with CallSomeFunc …
// use result when it's ready (this might block)
DoSomethingWith( result.value() );
The future allows us to decouple the call (launch) from the receiving of the result (join). This allows us full flexibility in how to launch the work without being invasive in either the API which remains synchronous and unaware, or the task handle which is represented as a simple and robust future. Also, we do not need to remember to call an explicit EndXxx method as the cleanup is encapsulated in the future abstraction (typically, the future object's destructor or disposer method).
Here's how it looks in the context of our original example. Note that there is no change to the original API:
// Example 4: Same original synchronous API.
//
RetType DoSomething(
InParameters ins,
OutParameters outs
);
// Sample asynchronous calling code
//
void CallerMethod() {
// …
// launch work asynchronously (in any
// fashion; for yuks let's use a thread pool)
// note that the types of "result" and
// "outTheOther" are now futures.
result = pool.run( ()=>{
DoSomething( this, that, outTheOther ) } );
// These could also take a long time
// but now run concurrently with DoSomething
OtherWork();
MoreOtherWork();
// … now use result.wait() (might block) and outOther…
}
Note: For convenience only, I'm showing the code using C# lambda syntax. If you don't have C++0x or C# lambdas available in your environment, you can still do this: Just replace "()=>" with a separate Runnable object (Java), delegate (C#), or functor (C++). The lambda is just syntactic sugar for writing a runnable or functor.
What if we don't want to wait for the result but just want some final steps to be performed whenever the call completes, as in Example 3? Easy: Just make it part of the async work, no callback required:
// Example 5 (compare with Example 3):
// Alternative calling code that doesn't wait
//
void CallerMethod() {
// …
// Launch, passing the input parameters.
// But don't join, just eventually use the result.
async( ()=>{
DoSomething( this, that, outTheOther );
// do whatever is necessary with result
// (write to disk, update a GUI text box, …)
} );
OtherWork();
MoreOtherWork();
// … now return without waiting for DoSomething
}
Summary
How should we supply an async version of an API? Often the best answer is to do nothing at all, because a caller can make any call asynchronous externally using features like "async" or task launching together with futures.
If you are providing a framework or library or any other API interface, prefer to keep asynchronous launching ("how") separate from the task to be done ("what"). This path leads to simpler APIs, simpler and more robust calling code, and great flexibility in where and how to execute the work.
Notes
[1] Calling Synchronous Methods Asynchronously
[2] In the past, people have sometimes used the word "future" mean both the asynchronous work and its result, but this is conflating two separate things. Always think of a future as just an asynchronous value or object
Herb Sutter is a bestselling author and consultant on software development topics, and a software architect at Microsoft. He can be contacted at www.gotw.ca.


