Awaiting a Good Example

Apparently there’s some confusion on how to use the new Async/Await capabilities of .NET 4.5. I know that there’s confusion on this point because I saw it on slides presented at a conference, and it made me sad. While what was claimed may be technically accurate for client apps, it looked dead wrong for web apps. So I started with one of K. Scott Allen’s articles on Ode To Code and modded his code a bit to demonstrate how Asyc/Await usually works.

Kicking open VS2012 and starting a WebApi project, I drop in the following code. Spoiler: it turns out that this is the wrong way to do it; keep reading.

public async Task<string[]> Get()
{
   var mark1 = await SleeperAsync(8000);
   string mark2 = "Timestamp2: " + DateTime.Now;
   var mark3 = await SleeperAsync(3000);
   string mark4 = "Timestamp4: " + DateTime.Now;

   return new string[] {  mark1, mark2, mark3, mark4 };
}

async Task<string> SleeperAsync(int napTime)
{
   string outVal = "TaskOne: " + DateTime.Now + " - ";
   await Task.Delay(napTime);
   return outVal + DateTime.Now;
}

And go to path /api/values (because this is the ValuesController) and see the entirely wrong results of

<ArrayOfstring xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<string>
TaskOne: 8/11/2012 11:35:20 AM - 8/11/2012 11:35:28 AM
</string>
<string>Timestamp2: 8/11/2012 11:35:28 AM</string>
<string>
TaskOne: 8/11/2012 11:35:28 AM - 8/11/2012 11:35:31 AM
</string>
<string>Timestamp4: 8/11/2012 11:35:31 AM</string>
</ArrayOfstring>

Which shows that awaiting a async method call makes it synchronous. Our first timestamp marks when the first delay finishes, and the second delay starts on that mark, then the second timestamp marks when the second delay finishes: synchronous. We’re awaiting the results (as we should’ve expected from the defintion of the word “await” but apparently C# is supposed to be the bizarro-world of natural language or something).

So let’s move one of those awaits so we can see exactly what’s going on:

public async Task<string[]> Get()
{
   var mark1 = SleeperAsync(8000); // await moved to result usage
   string mark2 = "Timestamp2: " + DateTime.Now;
   var mark3 = await SleeperAsync(3000); // We now know this is WRONG.
   string mark4 = "Timestamp4: " + DateTime.Now;

   return new string[] { await mark1, mark2, mark3, mark4 };
}

This gives us

<ArrayOfstring xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<string>
TaskOne: 8/11/2012 11:39:50 AM - 8/11/2012 11:39:58 AM
</string>
<string>Timestamp2: 8/11/2012 11:39:50 AM</string>
<string>
TaskOne: 8/11/2012 11:39:50 AM - 8/11/2012 11:39:53 AM
</string>
<string>Timestamp4: 8/11/2012 11:39:53 AM</string>
</ArrayOfstring>

And we can see that first task starts at 11:39:50, but doesn’t block the thread so our timestamp gets 11:39:50 as well and then the other task also starts at 11:39:50. But we’re awaiting the results of that other task, so it blocks the thread until 11:39:53, and then our other timestamp is also marked at 11:39:53. But we have to get that first (8-second task) to finish before we show any of it, so we’ve got an await down at the bottom and our patience is rewarded when the first task finishes at 11:39:58.

So the more-correct way to write the function is:

public async Task<string[]> Get()
{
   var mark1 = SleeperAsync(8000);
   string mark2 = "Timestamp2: " + DateTime.Now;
   var mark3 = SleeperAsync(3000);
   string mark4 = "Timestamp4: " + DateTime.Now;

   return new string[] { await mark1, mark2, await mark3, mark4 };
}

But! Spinning off async calls isn’t free and this “more” correct way of writing this function therefore is not optimal. The optimal thing to do is balance your long-running asynchronous calls with the overall amount of time that the rest of your still-synchronous code is running. So if I’ve got delays of 7, 3, 2, and 1 seconds, then I should only make the 7 second delay asynchronous since the 3, 2, and 1 second delays are only 6 seconds and the overall method is going to be waiting on the 7 second call to finish anyway.

But! This is still nonsense: who the heck puts 13 seconds of delays in their code? (People doing demos instead of real work, that’s who.) Your real code should require a lot more thought than this, and attempting to balance async to synch performance smacks of Premature Optimization. The rule on premature optimization is: Don’t Do It. More precisely, I say “don’t bother,” because as Miguel de Icaza observed: “You will be wrong about the things that actually mattered.” And while that often seems to be true in life and applies to teleological consequentialism, Miguel really was focused on software. So don’t bother doing it, your performance holes are going to be where you don’t expect them.

So the two things you should’ve learned here are:

  1. Putting await in front of an async call is Epiq Fale; the async method call goes first, the await shows up later at the point where you need the results.
  2. But all of this should be awaiting actual performance data from your code so that you don’t “optimize” something that’s already performing just fine.