The State of Async/Await

synchronization picture

A long time ago, asynchronous programming was an exotic practice.  Not many people were doing it, and their code was punctuated with things like assembly language and processor interrupts.  Less anciently, preemptive multitasking OS’s made asynchronous programming more accessible, albeit often still with arcane and unnatural boilerplate, not to mention hazards of sharing mutable data.

Today asynchronous programming is mainstream — more than that, it’s a firm expectation of polished software for desktop, mobile, and web.  The tools for asynchronous programming have gotten better, even as far as adding syntactic sugar, which

async/await

certainly is.

An Aside to Purists

Just to get it out of the way, let me go ahead and admit: this article is hopelessly procedural-language-normative, or at least popular-language-normative. I recognize that the purest approaches to asynchronous coding have been laid out by our bearded forebears in MIT labs in LISP and kindred languages based on immutable data; and that if everybody used Erlang the world wouldn’t have so many problems, and…

The above is all fine and good, but the languages most people actually use are C++, C#, Javascript, Python, Swift and the like, so that’s the focus of this review.

What are Coroutines?

Coroutines play ping pong with each other in the order of executing their code. In other words, Code A executes until it reaches a deferral instruction, then it yields execution to Code B which executes until it reaches a deferral instruction, then yields back to Code A, and so forth. The difference between coroutines versus two unrelated parallel threads of execution is that with coroutines, control is explicitly passed back and forth, so that the coroutine author can allow one routine to handle outputs of the other.

According to a quote attributed to Don Knuth, any subroutine is a special case of a coroutine, specifically a coroutine that only performs one exchange with its calling process instead of many.

Building On Enumerators

A simple kind of coroutine is an iterator or generator. Here’s a Python example:

def GetSpecialNumbers():
   i = 0
   while True:
      if IsSpecial(i):
         yield i

def ProcessNumbers():
   for specialNum in GetSpecialNumbers():
      print("Found a special number: {0}".format(specialNum))

The above code is a coroutine, because it “ping pongs” back and forth between the outer

ProcessNumbers()

routine and the inner

GetSpecialNumbers()

routine. The inner function’s stack and state are preserved every time it

yield

s control to the outer caller, and if enumerated further, it seamlessly picks up its old state to enumerate or generate further.

However, the example above is synchronous, not asynchronous, as it will block execution while the inner function searches for valid return values. That’s not very interesting. How about an asynchronous example?

A Truly Asynchronous Coroutine

Compare the following truly asynchronous code written in C#:

async Task<int> GetNextSpecialNumber(int startValue)
{
   return await Task.Run(() => 
   {
      for (int i = startValue; true; ++i)
      {
         if (IsSpecial(i))
         {
            return i;
         }
      }
   });
}

async Task ProcessNumbersAsync()
{
   int nextSpecialNum = 0;
   while (true)
   {
      nextSpecialNum = await GetNextSpecialNumber(nextSpecialNum);
      Console.WriteLine("Found a special number: {0}", specialNum);
      ++nextSpecialNum;
   }
}

This time whoever calls

ProcessNumbersAsync()

will immediately get back a

Task

instance, and the knowledge that somewhere, somehow, the invoked code may be still executing asynchronously. Thus the caller won’t be blocked, and can use that

Task

instance to interact with the coroutine. Perhaps the caller would

await

that task, just as

ProcessNumbersAsync()
await

s

GetNextSpecialNumber()

.

Less Generator, More Worker

Notice also that the C# version above is not written like the Python enumerator. While I guess we could have our routine enumerate a type of IEnumerable<Task>, that is not a very common or useful way to think about this, since the heavyweight multithreading we are employing suggests a best practice of offloading maximized chunks of work, not engaging with chatty enumerators.

In the real world, the most common reason for using

async/await

is to spare the UI thread from blocking. You could extend that to any other thread and say that the purpose is to easily interact with coroutines that run on other threads, while ensuring you always resume on your original thread.

This is particularly useful in mobile development where it is critically important to interact with the UI only on the UI thread, while delegating disk or network IO to a secondary worker thread.

Mitigating Race Conditions

One of the banes of multithreaded or asynchronous programming over the years has been shared state between threads, and the need to protect that state from corruption due to race conditions. Presumably the best way to solve that problem is to ditch our whole ecosystem and go with an immutable message passing scheme. That certainly excludes common categories of async bugs, at the cost of coding in a fringe language. However as mentioned above, we can’t go there for this article, so let’s look at best practices for mitigation with the tools we have.

One of the benefits of C#’s

async/await

is that you are guaranteed that the

async

function always runs on the same thread, unless it happens to manually spin up a worker. When the

async

function awaits another

async

function, it may be that the other function spins up a worker thread. However when control returns to us after an

await

, it will always be on our original thread.

Besides the obvious benefit of being able to trivially inspect mobile code to be sure it is running on the UI or main thread, this also can help with state integrity. As long as all the hard and time consuming calculations are being done on the worker thread, and the worker thread returns its results to us, we can just take the results of the worker and update the app’s shared state in the outer

async

routine. That way we don’t need to worry about state being corrupted due to two or more threads directly accessing it.

Doing It Wrong


dangerous techniques
Image credit: thewebprincess on Flickr under CC

Powerful tools are often dangerous for the same reasons they are powerful. You can use asynchronous programming to shoot yourself in the foot if you don’t stay on well trod paths. For example, with C#’s

async/await

, as long as you just use the keywords

async/await

and perhaps an occasional

ContinueWith()

for branching asynchronicity, you will be fine.

The temptation of a beginner is to invoke those methods of a

Task

/coroutine that block, such as the

Result

property or the

Wait()

method. There are reasons those methods exist, but 99% of the time a newbie invokes them, the methods will deadlock due to the way the thread pooling works. Newbies are well advised to only use

async/await

and maybe occasionally

ContinueWith()

, but never to touch the blocking stuff.

Brevity

Earlier I referred to

async/await

as syntactic sugar, which it is. As such, its chief benefit is reducing the levels of indentation for complex async invocations, and simplifying the exception handling. After getting used to

async/await

, you start to feel sorry for people who have to write asynchronous code without it — sometimes including oneself depending on one’s current project.

Async code written “the old way” suffers from wordiness, and lends itself to a toxic compounding of complexity in indentation and error handling logic. Often it just turns into spaghetti, whereas

async/await

can reverse that trend quite elegantly.

Language Survey: C#

For better or worse, C# is the standard bearer with

async/await

. The merits of its particular flavor of asynchronous coroutines may be debated as all programming languages are, however C# has certainly affected design choices in newer languages and language revisions.

async Task<bool> CheckIfJqueryUsed()
{ 
    HttpClient client = new HttpClient();
    Task<string> getArtAndLogicTask = client.GetStringAsync("https://www.alproduction.local");
    Task<string> getAppleTask = client.GetStringAsync("https://www.apple.com");

    // If we like, we can perform some long running operation here while we wait
    // for the above tasks to independently complete.
    DoSomethingLongRunningIfWeLike();

    // Awaiting will suspend execution within this function until the given task
    // is complete.  While awaiting, control returns to the caller of this function.
    string appleSiteContent = await getAppleTask;
    string anlSiteContent = await getArtAndLogicTask;

    // OK, so a bit of a silly example.
    return appleSiteContent.Contains("jquery") && anlSiteContent.Contains("jquery");
}

 

Language Survey: C++

Many developers perceive that C++ has undergone a renaissance of late, seeing inclusion of such traditionally high level features as lambdas, generics, and

auto

type inference. Microsoft has experimented in their Code Technology Preview (CTP) releases with

resumable/await

for Visual C++, and more recently a

future/generator/await

scheme.

The official C++ language change to support coroutines has been deferred and didn’t make it in time for C++17. Here’s a link to what it may eventually look like, using new keywords of

co_await

,

co_yield

, and

co_return

.

An example from Microsoft using the latest proposal:

#include <windows.h>
#include <future>
#include <iostream>
 
auto operator await(std::chrono::system_clock::duration duration) {
  class awaiter {
    static void CALLBACK TimerCallback(PTP_CALLBACK_INSTANCE, void *Context, PTP_TIMER) {
      std::experimental::coroutine_handle<>::from_address(Context)();
    }
    PTP_TIMER timer = nullptr;
    std::chrono::system_clock::duration duration;
  public:
    explicit awaiter(std::chrono::system_clock::duration d) : duration(d) {}
    bool await_ready() const { return duration.count() <= 0; }
    bool await_suspend(std::experimental::coroutine_handle<> resume_cb) {
      int64_t relative_count = -duration.count();
      timer = CreateThreadpoolTimer(TimerCallback, resume_cb.to_address(), nullptr);
      SetThreadpoolTimer(timer, (PFILETIME)&relative_count, 0, 0);
      return timer != 0;
    }
    void await_resume() {}
    ~awaiter() { if (timer) CloseThreadpoolTimer(timer); }
  };
  return awaiter{ duration };
}
 
using namespace std;
using namespace std::chrono;
 
future<void> test() {
  cout << this_thread::get_id() << ": sleeping...\n";
  await 1ms;
  cout << this_thread::get_id() << ": woke up\n";
}
 
int main() {
  test().get();
  cout << this_thread::get_id() << ": back in main\n";
}

 

Language Survey: Javascript

For all of its existence, Javascript has been ground zero of asynchronous programming. Particularly in the browser environment, AJAX requests are asynchronous, and there are a million ways javascript programmers and library authors have wrapped such async logic.

There has been a push to include async/await explicitly in the ECMAScript language for revision ES2016 that has been welcomed in many quarters. However, that seems not to have happened pending vendor/browser adoption that seems to be the sine qua non of javascript standardization.

Notably, some also think it would be a mistake to include async/await in javascript as compared to more general purpose asynchronous coroutines.

Here’s a cool example of what it could look like from Eddie Zaneski’s blog linked above:

var request = require('request');
 
function getQuote() {
  return new Promise(function(resolve, reject) {
    request('http://ron-swanson-quotes.herokuapp.com/v2/quotes', function(error, response, body) {
      if (error) return reject(error);
      resolve(body);
    });
  });
}
 
async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch(error) {
    console.error(error);
  }
}
 
main();
console.log('Ron once said,');

 

Language Survey: Python

Async/await has already seen adoption in Python 3.5 via PEP 492. It is self consciously a product both of its roots in Pythonic generators and also of the C# pattern.

Because Python’s

async/await

is truly asynchronous, it requires a message loop. Here’s an example from the PEP:

import asyncio

async def echo_server():
    print('Serving on localhost:8000')
    await asyncio.start_server(handle_connection,
                               'localhost', 8000)

async def handle_connection(reader, writer):
    print('New connection...')
    while True:
        data = await reader.read(8192)
        if not data:
            break
        print('Sending {:.10}... back'.format(repr(data)))
        writer.write(data)

loop = asyncio.get_event_loop()
loop.run_until_complete(echo_server())
try:
    loop.run_forever()
finally:
    loop.close()

 

Language Survey: Swift

Objective C is pretty much a language that only its mother could love, and accordingly Apple’s swank replacement has been greeted with much fanfare and enthusiasm in the iOS and Mac development community. Unfortunately or fortunately depending on your point of view, Apple has been slow and cautious about rolling out new Swift language feaures. Swift supports Apple’s Grand Central Dispatch for asynchronous programming, but not yet any syntactic sugar like async/await.

That hasn’t stopped Swift enthusiasts from writing their own async/await- like features, however. Early attempts were limited to following the form of C# async/await while actually blocking execution, which misses a major point of the exercise. Later research has been promising, albeit somewhat patched together and bleeding edge. It is likely that Apple would need to create an elegant final solution, though this may be complicated by Apple’s architectural choices inherited from Obj-C and implicit to the standard Apple frameworks.

An example from the above link:

let task = async { () -> () in
  let fetch = async { (t: Task<NSData>) -> NSData in
    let req = NSURLRequest(URL: NSURL.URLWithString("http://www.google.com"))
    let queue = NSOperationQueue.mainQueue()
    var data = NSData!
    NSURLConnection.sendAsynchronousRequest(req,
                                            queue:queue,
      completionHandler:{ (r: NSURLResponse!, d: NSData!, error: NSError!) -> Void in
        data = d
        Async.wake(t)
      })
    Async.suspend()
    return data!
  }

  let data = await(fetch)
  let str = NSString(bytes: data.bytes, length: data.length,
                     encoding: NSUTF8StringEncoding)

  println(str)
}

 

yield return

This has been an informal review of C#’s particular flavor of asynchronous coroutines using the

async/await

keywords, and how that construct has infected(?) other programming languages where it seems to be evolving in parallel. I welcome a future where asynchronous programming is made ever easier and where the common use cases are easy to get right and hard to get wrong. End users will appreciate the smoother experience, too.

Andrew Vogan

Andrew Vogan

A Senior Software Engineer with A&L who develops for Windows and Mac desktop, iOS, Android, and web, plus some kernel drivers.
Andrew Vogan

Latest posts by Andrew Vogan (see all)

Tags:

Creative Commons License

This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.