Getting Multiple or Interim Results
All of the aforementioned options deal well with return values and output parameters. Finally, however, what if we want to get multiple notifications before the final results, such as partial computation results, updated status such as progress updates, and so on?
We have two main options:
- Provide an explicit message queue or channel back to the caller, which can enqueue multiple results.
- Accept a callback to invoke repeatedly to pass multiple results back to the caller.
Example 4 will again use the callback approach. If the caller is itself an active object and the callback it provides is one of its own (asynchronous) methods, we've really combined the two paths and done both bullets at the same time. (Note: Here we're focusing on the interim progress via the statusCallback; for the return value, we'll again just use a "future" as in Examples 1 and 2.)
// Example 4: Returning partial results/status
class Backgrounder {
public:
// Print() puts print result into spooler, returns one of:
// Error (failed, couldn't process or send to spooler)
// Printing (sent to spooler and already actively printing)
// Queued (sent to spooler but not yet actively printing)
future<PrintStatus>
Print(
Data& data,
function<void(PrintInfo)> statusCallback
) {
auto p = make_shared<promise<PrintStatus>>();
future<PrintStatus> ret = p->get_future();
a.Send( [=, &data] {
PrintInfo info;
while( /* not done formatting the data */ ) {
info.SetPercentDone( /*…*/ );
statusCallBack( info ); // interim status
// … do the printing work for another piece of the data …
} while( /* not done formatting the data */ );
p->set_value( /* … */ ); // set final result
info.SetPercentDone( 100 );
statusCallBack( info ); // final interim status
} );
return ret;
}
This might be used in the GUI thread example as follows:
class MyGUI {
public:
// …
// When the user clicks [Print]
void OnPrintClick() {
// …
// … turn on printing icon, etc. …
// …
// pass a continuation to be called to give
// us the result once it's available
shared_future<PrintStatus> result;
result = backgrounder.Print( theData,
< [=]( PrintInfo pi ) { SendPrintInfo( pi, result ); } );
}
void OnPrintInfo(
PrintInfo pi,
shared_future<PrintStatus> result
) {
// … update print progress bar to
// pi.GetPercentDone(), etc. …
// if this is the last notification
// (100% done, or result is ready)
if( result.is_ready() ) {
// … turn off printing icon, etc. …
}
}
Summary
To express return values and "out" parameters from an asynchronous function, including an active object method, either:
- Return a "future" to invoke that the caller can "pull" the result from (Example 1) or convert it to a "push" (Examples 2(a) and 2(b), and prefer to use
ContinueWithwhere available); or - Accept a callback to invoke to "push" the result to the caller when ready (Example 3).
To return multiple partial results, such as partial computations or even just "percent done" progress information, also use a callback (Example 4).
Whenever you provide callbacks, remember that they are running in the callee's context, so we want to keep them as short and noninvasive as possible. One good practice is to have the callback just fire off asynchronous messages or method calls and return immediately.
On Deck
Besides moving work off to a background thread, what else could we use an active object for? We'll consider an example next time, but for now, here's a question for you to ponder: How might you use an active object to replace a mutex on some shared state? Think about ways you might approach that problem, and we'll consider an example in my next column.
References
[1] H. Sutter. Prefer Using Active Objects Instead of Naked Threads. Dr. Dobb's Digest, June 2010.
[2] H. Sutter. Prefer Futures to Baked-In 'Async APIs'. Dr. Dobb's Digest, January 2010.
[3] Task.ContinueWith Method (Action<Task>); MSDN.
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.


