Channels ▼
RSS

Design

Prefer Futures to Baked-In "Async APIs"


Option 1: Explicit Begin/End (Poor)

One option is for the API itself to provide an asynchronous version of the function. This is such a popular option that there are patterns for it, each of which has been reinvented with variations a number of times.

One form of pattern that is commonly used in .NET (as well as reinvented in other places, such as in C++ code inside Microsoft Office) is called the Frameworks Async Pattern. The idea in this pattern is to split the original method into a pair of methods, one to begin (launch) the work, the other to end (join with) the work. In a moment we'll see how this can get elaborate, with intermediate structures and explicit callback registrations, but here's the simple version:


// Example 2: Applying the basic "Async Pattern"
//
// The "Begin" part takes the input parameters.
//
IAsyncResult BeginDoSomething(
  InParameters ins
);
// The "End" part yields the return value and
// output parameters. Note: The caller must
// explicitly call EndDoSomething.
//
RetType  EndDoSomething(
  IAsyncResult asyncResult,
  OutParameters outs
);

Here's how the earlier calling code would look, using this BeginXxx/EndXxx pattern to call DoSomething asynchronously:


// Example 2, continued:
// Sample calling code
//
void CallerMethod() {
  // …

  // Launch, passing the input parameters.
  IAsyncResult ar = BeginDoSomething( this, that );
  result = DoSomething( this, that, outTheOther );

  // These could also take a long time
  // but now run concurrently with DoSomething
  OtherWork();
  MoreOtherWork();

  // Join, then call End to get the return
  // value and output parameters.
  ar.AsyncWaitHandle.WaitOne();
  result = EndDoSomething( ar, outtheOther );

  // … now use result and outOther …
}

This approach works. However, it has some drawbacks.

First, this pattern is intrusive and bloats the API. The API surface area has now tripled for calls that could be asynchronous, because instead of one function we have three that must be kept in sync. There is also the issue of consistency: The pattern may vary in that it depends on manual discipline for each API author to do it the same way each time. Further, the API designer has to know in advance which methods should support asynchronous calling.

We can eliminate this problem by providing a generalized form of the begin/end pattern that doesn't need to be baked into a specific API. For example, the .NET Framework provides a generalized BeginInvoke/EndInvoke that can call arbitrary methods asynchronously. [1] Unfortunately, this does not address the other drawbacks below.

Second, this approach becomes complex for callers, who instead of calling one function must now call three functions: one each to begin, to wait, and to end. It also has more potential points of failure where the calling code can go wrong. For example, if you end up deciding you don't need the result after all because your work is being cancelled or you called the function speculatively or for any other reason, do you have to call the EndXxx method? Typically, yes (and on .NET specifically, always yes), because the BeginXxx call will allocate resources to launch and track the work, and the EndXxx call releases those resources. Hence, the .NET Design Guidelines document says: "Do ensure that the EndMethodName method is always called even if the operation is known to have already completed. This allows exceptions to be received and any resources used in the async operation to be freed." This is a common source of errors as even expert programmers, and books written by true gurus, have got this wrong by thinking the EndXxx call was optional, or just forgetting to write it on all paths.

Third, this approach actually grows more complex because following this path leads to more complex and ornate styles. In particular, if you look at the actual implementations the begin/end pattern actually grows more parts:


// Example 3: The fuller "Async Pattern"
//
// The "Begin" part takes the input parameters
// and optionally a callback to be invoked
// at the end of the work, and a cookie to
// identify this invocation.
//
IAsyncResult BeginDoSomething(
  InParameters ins,
 // + caller can supply a callback notification
  AsyncCallback callback,
  // + extra state to identify "this call"
  Object cookie
);
// The "End" is the same as in Example 2.
RetType EndDoSomething(
  IAsyncResult asyncResult,
  OutParameters outs
);

The reason that we grow this requirement is so that callers can provide a kind of "finally do X with the result" handler in cases where the caller wants to return without necessarily needing or wanting to wait for the async call to complete. For example:


// Example 3, continued:
// Alternative calling code that doesn't wait
//
void CallerMethod() {
  // …

  // Launch, passing the input parameters.
  // But don't join, just eventually use the result.
  IAsyncResult ar = BeginDoSomething( this, that,
    (Object myExtraState)=>{
      result = EndDoSomething( ar, outtheOther );
      // do whatever is necessary with result
      // (write to disk, update a GUI text box, …)
    },
    new MyExtraState( /*…*/ )
  );
  result = DoSomething( this, that, outTheOther );

  OtherWork();
  MoreOtherWork();

  // … now return without waiting for DoSomething
}

Fourth, and finally, all variations of this begin/end approach hardwire a specific way to perform the asynchronous call. The caller can choose to run the work asynchronously, but cannot choose how to run it — whether to run the work on a thread pool thread, on a fresh new thread, as a task on an automatically load-balanced work stealing runtime, on a particular processor core, and so on.

We definitely want to decouple the idea of "run this work asynchronously" ("how" to call) from any given API itself ("what" to call). We partly achieved that much with the generalized form of the begin/end pattern. But we want even more: We want to make the calling code simpler and more robust, and give the caller the flexibility of launching the work in arbitrary ways.

The good news is that we can do better.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video