Eduasync part 9: generated code for multiple awaits

Last time we looked at a complex async method with nested loops and a single await. This post is the exact opposite – the method is going to look simple, but it will have three await expressions in. If you’re glancing down this post and feel put off by the amount of code, don’t worry – once you’ve got the hang of the pattern, it’s really pretty simple.

Here’s the async method we’re going to analyze:

private static async Task<int> Sum3ValuesAsyncWithAssistance()
{
    Task<int> task1 = Task.Factory.StartNew(() => 1);
    Task<int> task2 = Task.Factory.StartNew(() => 2);
    Task<int> task3 = Task.Factory.StartNew(() => 3);


    int value1 = await task1;
    int value2 = await task2;
    int value3 = await task3;

    return value1 + value2 + value3;
}

Nice and straightforward: start three tasks, then sum their results. Before we look at the decompiled code, it’s worth noting that writing it this way allows the three (admittedly trivial tasks) to run in parallel. If we’d written it this way instead:

private static async Task<int> Sum3ValuesAsyncWithAssistance()
{
    // No parallelism…
    int value1 = await Task.Factory.StartNew(() => 1);
    int value2 = await Task.Factory.StartNew(() => 2);
    int value3 = await Task.Factory.StartNew(() => 3);

    return value1 + value2 + value3;
}

… then we’d have waited for the first task to finish before starting the second one, then waited for the second one to complete before we started the third one. That’s appropriate when there are dependencies between your tasks (i.e. you need the result of the first as an input to the second) and it would still have been asynchronous but when you can start multiple independent tasks together, that’s generally what you want to do. Don’t forget that this doesn’t just extend to CPU-bound tasks – you might want to launch tasks making multiple web service calls in parallel, before collecting the results.

As before, I’ll just show the heart of the generated code, without its boiler-plate skeleton. Again, the full code is available online.

Generated code

  switch (state)
  {
      case 1:
          break;

      case 2:
          goto Label_Awaiter2Continuation;

      case 3:
          goto Label_Awaiter3Continuation;

      default:
          if (state != -1)
          {
              task1 = Task.Factory.StartNew(() => 1);
              task2 = Task.Factory.StartNew(() => 2);
              task3 = Task.Factory.StartNew(() => 3);

              awaiter1 = task1.GetAwaiter();
              if (awaiter1.IsCompleted)
              {
                  goto Label_GetAwaiter1Result;
              }
              state = 1;
              doFinallyBodies = false;
              awaiter1.OnCompleted(moveNextDelegate);
          }
          return;
  }
  state = 0;
Label_GetAwaiter1Result:
  int awaitResult1 = awaiter1.GetResult();
  awaiter1 = new TaskAwaiter<int>();
  value1 = awaitResult1;

  awaiter2 = task2.GetAwaiter();
  if (awaiter2.IsCompleted)
  {
      goto Label_GetAwaiter2Result;
  }
  state = 2;
  doFinallyBodies = false;
  awaiter2.OnCompleted(moveNextDelegate);
  return;
Label_Awaiter2Continuation:
  state = 0;
Label_GetAwaiter2Result:
  int awaitResult2 = awaiter2.GetResult();
  awaiter2 = new TaskAwaiter<int>();
  value2 = awaitResult2;

  awaiter3 = task3.GetAwaiter();
  if (awaiter3.IsCompleted)
  {
      goto Label_GetAwaiter3Result;
  }
  state = 3;
  doFinallyBodies = false;
  awaiter3.OnCompleted(moveNextDelegate);
  return;
Label_Awaiter3Continuation:
  state = 0;
Label_GetAwaiter3Result:
  int awaitResult3 = awaiter3.GetResult();
  awaiter3 = new TaskAwaiter<int>();
  value3 = awaitResult3;

  result = value1 + value2 + value3;

Like last time, I’ll just go through the interesting points this raises, rather than examining it line by line.

Switch instead of if

In all our previous async methods, we’ve only had three possible states: -1 (finished), 0 (normal), 1 (return from continuation). The generated code always looked something like this to start with:

if (state != 1) 

    if (state == -1) 
    { 
        return
    } 
    // Execute code before the first await, loop or try
}

The comment is somewhat brief here, but the basic idea is that all the code which will only ever execute the first time (with no continuations) can go here. If there’s a loop that contains an await, then a continuation would have to jump back into that loop, so that code couldn’t be contained within this initial block. (A loop which didn’t have any awaits in could though.)

Anyway, this time we don’t have an "if" statement like that – we have a switch. It’s the same idea, but we could be in any of three different states when a continuation is called, depending on which await expression we’re at. The switch statement efficiently branches to the right place for a continuation, and executes the initial code otherwise. The "branch" for state 1 is just to exit the switch statement and continue from there.

It’s possible that the generated code actually has more levels of indirection than it needs; I don’t know about the details of what’s allowed within an IL switch, but it seems odd to effectively have an "On X goto Y" where Y immediately performs a "goto Z". If the switch statement could branch immediately to the right label, we’d end up with IL which probably wouldn’t have a hope of being decompiled to C#, but which might be slightly more efficient. It’s quite likely that the JIT can sort all of that out, of course.

I tend to actually think about all of this as if the code that’s really in the "default" case for the switch statement appeared after the switch, and the default case just contained a goto statement to jump to it. The effect would be exactly the same, of course – but it means I have a mental model of the method consisting of a "jump to the right place" phase before an "execute the code" phase. Just because I think of it that way doesn’t mean you have to, of course :)

Multiple awaiters and await results

There’s room for a bit more optimization in this specific case. We have three awaiters, but they’re all of the same type (TaskAwaiter<int>). Likewise, we have three await results, but they’re all int. (It would be possible to have different awaiter types with the same result type, of course.)

In the CTP (at least without optimization enabled) we end up with an awaiter / awaitResult pair of variables for each await expression. There’s never more than one await "active" at any one time, so the C# compiler could generate one awaiter variable per awaiter type, and one result variable per result type. In the common situation where the result is being directly assigned to a "local" variable of the same type within the method, we don’t really need the result variable at all. On the other hand, it’s only a local variable (unlike the awaiter) and it’s quite possible that the JIT can optimize this instead.

Ultimately it’s entirely reasonable for the C# compiler to be generating suboptimal code at this point in the development cycle. After all, it could be quite easy to introduce bugs due to inappropriate code generation… as we’ll see next time.

Conclusion

Other than the different way of getting to the right place on entry (using a switch instead of an if statement), async methods with multiple await expressions aren’t that hard to follow. Of course when you combine multiple awaits with loops, try/catch, try/finally blocks and any number of other things you might use to complicate the async method, things become tricky – but with the fundamentals covered in this blog series, hopefully you’d be able to cope with the generated code in any reasonable situation. Of course, it’s rare that you’ll need (or want) to look at the generated code in anything like the detail we have here – but now we’ve looked at it, you don’t need to wonder where the magic happens.

The next post will be the last one involving the decompiled code, at least for the moment. I’d like to demonstrate a bug in the CTP – mostly to show you how a small change to the async method can trigger the wrong results. I’m absolutely positive it will be fixed before release – probably for the next CTP or beta – but I think it’s interesting to see the sort of situation which can cause problems.

After that, we’re going to look at exception handling, before we move into a few odd way of using async – in particular, implementing coroutines and "COMEFROM".

Eduasync part 8: generated code from a complex async method

Last time we had a really simple async method, and looked at the generated code. Even that took a little while… but in this post I’ll demonstrate some of the intricacies that get involved when the async method is more complex. In particular, this time we have:

  • A parameter to the async method
  • A "using" statement, so we’ll need a finally block
  • Two loops
  • The possibility of catching an exception generated from await and retrying

I’ve not made it as complicated as I could have done, mind you – we’ve still only got one await expression. Here’s the async method we’ll be investigating:

private static async Task<int> WriteValuesAsyncWithAssistance(int loopCount)
{
    using (TextWriter writer = File.CreateText("output.txt"))
    {
        int sum = 0;
        for (int i = 0; i < loopCount; i++)
        {
            Task<int> valueFetcher = Task.Factory.StartNew(() => 1);

            for (int j = 0; j < 3; j++)
            {
                try
                {
                    Console.WriteLine("Awaiting…");
                    int value = await valueFetcher;
                    writer.WriteLine("Got value {0}", value);
                    sum += value;
                    break; // Break out of the inner for loop
                }
                catch (Exception)
                {
                    Console.WriteLine("Oops… retrying");
                }
            }
        }
        return sum;
    }
}

Okay, so it’s already reasonably complicated even as a normal method. But it gets nastier when we look at the code generated for it. In particular, this time the decompiled code actually isn’t quite valid C#. I’ll get to that in a minute. I’ve split the rest of this post into several smallish chunks to try to help keep several concerns separated. They’re not particularly pleasantly balanced in terms of size, segue etc… but hopefully they’ll make things simpler.

The decompiled code

I’m going to ignore the standard skeleton – basically everything you see here is within a try/catch block so that we can call SetException on the builder if necessary, and if we reach the end of the method normally, we’ll call SetResult on the builder using the "result" variable. If you want to see the complete method, it’s in the source repository. Here’s the cut down version:

bool doFinallyBodies = true;
int tmpState = state;

// Effectively a three way switch: 1, -1, 0
if (tmpState != 1)
{
    if (state == -1)
    {
        return;
    }
    this.writer = File.CreateText("output.txt");
}
try
{
    tmpState = state;
    if (tmpState == 1)
    {
        goto Label_ResumePoint;
    }
    sum = 0;
    i = 0;
  Label_ResumePoint: // This shouldn’t quite be here… see below
    while (i < loopCount)
    {
        // Not in generated code:
        if (state == 1)
        {
            goto Label_ResumePoint2;
        }
        // Back to generated code

        valueFetcher = Task.Factory.StartNew(() => 1);
        j = 0;

        // Still not in the generated code, and still not quite right… we don’t want the j test here
      Label_ResumePoint2:
        // Back to generated code again…
        while (j < 3)
        {
            try
            {
                // We want Label_ResumePoint to be here really
                tmpState = state;
                if (tmpState != 1)
                {
                    Console.WriteLine("Awaiting…");
                    awaiter = valueFetcher.GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        state = 1;
                        doFinallyBodies = false;
                        awaiter.OnCompleted(moveNextDelegate);
                        return;
                    }
                }
                else
                {
                    state = 0;
                }
                int awaitResult = awaiter.GetResult();
                awaiter = new TaskAwaiter<int>();
                value = awaitResult;
                writer.WriteLine("Got value {0}", value);
                sum += value;
                break;
            }
            catch (Exception)
            {
                Console.WriteLine("Oops… retrying");
            }
            j++;
        }
        i++;
    }
    result = sum;
}
finally
{
    if (doFinallyBodies && writer != null)
    {
        writer.Dispose();
    }
}

Now that’s a fair bit of code… but if you were comfortable with the last post, there shouldn’t be much which is surprising. I’m not going to go over every aspect of it – just the surprising twists introduced by the extra complexities of the async method.

State and tmpState

Okay, let’s start from a point of ignorance: I don’t know why a local variable (tmpState) is introduced to take a copy of the state. My guess is that in some really complex scenarios it either becomes relevant when we want to change the state but also know what the state was, or if we’re testing the state several times without changing it, it may be quicker to do so having copied it to a local variable. I think we can pretty much ignore it – I’ve only included it here for completeness.

To do finally bodies or not to do finally bodies, that is the question

Last time the doFinallyBodies variable was pointless because we didn’t have any finally blocks – this time we do (albeit just the one). It’s implicit due to the "using" statement in the original code, but that’s really just a try/finally. The CreateText call occurs before we really enter into the using statement, which is why it’s before the try block in the decompiled code.

Broadly speaking, a try/finally block in an async method corresponds to the same try/finally block in the generated code, with one big difference: if we’re leaving the method because we’re just returning having attached a continuation to an awaiter, we don’t want to execute the finally body. After all, the method is effectively "paused" in the block rather than really exiting the block. So, any time you see an OnCompleted call, you’ll see a "doFinallyBodies = false;" statement… and that flag will be set to true at the start of the method, so that at any other time, exiting the try block will cause the finally body to be executed. We still want the body to execute if there’s an exception, for example. This is the same pattern used by the generated code for iterator blocks. (That’s extremely unsurprising at the moment, as the code generators share a lot of the code. It’s one bit which will have to be preserved if the two generators are split though.)

Go to where exactly?

As I mentioned earlier, we can’t write C# code which exactly represented the generated IL. That’s because when we enter the method in state 1 (i.e. for a continuation) the actual generated IL performs a goto straight into the start of the try block. You can’t do that in straight C#, which is why I’ve got an ugly double-goto in my representation. It doesn’t make it a 100% accurate representation of the IL, but it’s close enough for our purposes. If you build the async method and look at the generated code in Reflector, you’ll see that it basically shows a jump to the final comment above, just inside the try block. (Reflector actually shows the label as occurring before the try block, but the branch instruction in the IL really does refer to the first instruction in the try block.)

Now, you may be wondering why the generated code only jumps to the start of the try block. Why doesn’t it just jump straight to the "await" part? Last time it jumped straight to the "state = 0;" statement, after all… so we might expect it to do the same thing here.

It turns out that although IL has somewhat more freedom than C# in terms of goto (branch) statements, it’s not entirely free of restrictions. In this case, I believe it’s running up against section 12.4.2.8.2.7 of ECMA-335 (the CLI specification) which states:

If any target of the branch is within a protected block, except the first instruction of that protected block, the source shall be within the same protected block.

[…]

[Note: Code can branch to the first instruction of a protected block, but not into the middle of one. end note]

For "protected block" we can read "try block" (at least in this case).

I really shouldn’t enjoy the fact that even the C# compiler can’t do exactly what it would like to here, but somehow it makes me feel better about the way that the C# language restricts us. It’s possible that anthropomorphizing the C# compiler at all is a bad sign for my mental state, but never mind. It gets even worse if an await expression occurs in a nested try block – another "if" is required for each level involved.

The point is that by hook or by crook, when a continuation is invoked, we need to reach the assignment back to state 0, followed by the call to awaiter.GetResult().

Why do we go back to state 0?

You may have noticed that when we return from a continuation, we set the state to 0 before we call GetResult(). We only ever set to the state to a non-zero value just adding a continuation, or when we’ve finished (at which point we set it to -1). Therefore it will actually be 0 whenever we call GetResult() on an awaiter. But why? Shouldn’t state 0 mean "we’re just starting the method"? We know that state -1 means "the method has finished" and a positive state is used to indicate where we need to come back to when the continuation is called… so what sense does it make to reuse state 0?

This bit took me a while to work out. In fact, it had me so stumped that I asked a question on Stack Overflow about it. Various theories were presented, but none was entirely satisfactory. At the same time, I mailed Mads Torgersen and Lucian Wischik about it. After a while, Lucian got back to me and assured me that this wasn’t a bug – it was very deliberate, and absolutely required. He didn’t say why though – he challenged me to work it out, just in the knowledge that it was required. The example I eventually came up with was a slightly simpler version of the async method in this post.

The key is to think of states slightly differently:

  • -1: finished
  • 0: running normally; no special action required
  • Anything else: Waiting to get back to a continuation point (or in the process of getting there)

So why must we go back to the "running normally" state as soon as we’ve reached the right point when running a continuation? Well, let’s imagine the flow in a situation like the async method shown, but where instead of just creating a trivial task, we’d started one which could fail, and does… but hasn’t finished before we first await it. Ignoring the outer loop and the using statement, we have something like this:

  1. The task is created
  2. We enter the inner loop
  3. We write out "Awaiting…" on the console
  4. We get an awaiter for the task, and check whether it’s completed: it hasn’t, so we add a continuation and return
  5. The task completes, so we call GetResult() on the awaiter
  6. GetResult() throws an exception, so we log it in the catch block
  7. We go back round the inner loop, and want to re-enter step 3

Now look at the flow control within the decompiled method again, and imagine if we hadn’t set the state to 0. We’d still be in state 1 when we reached the catch block, so when we got back to the top of the loop, we’d ignore the Console.WriteLine call and not even get a fresh awaiter – we’d end up reusing the same awaiter again. In other words, we’d try to get back to the continuation point.

To think of it another way: by the time we get back to the call to awaiter.GetResult(), the two possible paths (executing immediately vs adding a continuation, based on the value of awaiter.IsCompleted) have merged and we want to behave exactly the same way from that point onwards. So naturally we’d want to be in the same state… which is 0 for the synchronous path, as we haven’t had any reason to change state.

Conclusion

All this complexity, and just with one await expression! As is so often true in computing, it all seems a bit simpler when you can look past it being a wall of code, and break down each lump separately. Hopefully I’ve taken some of the surprises out of the generated code here, although please do leave a comment if anything is still unclear – I’ll try to update the post if so.

Next time we’ll look at an async method which is generally simpler, but which has three await expressions.

Eduasync part 7: generated code from a simple async method

In part 6 we tried to come up with a "manual" translation of a very simple async method. This time we’ll see what the compiler really generates.

As a reminder, here’s the async method in question:

private static async Task<int> ReturnValueAsyncWithAssistance() 

    Task<int> task = Task<int>.Factory.StartNew(() => 5); 

    return await task; 
}

Whereas our manual code still ended up with a single method (admittedly involving a lambda expression), the generated code is split into the method itself (which ends up being quite small), and a whole extra class representing a state machine.

The "stub" method

We looked at a similar translation before, but didn’t really explain it. This time let’s take a closer look:

private static Task<int> ReturnValueAsyncWithStateMachine()
{
    StateMachine stateMachine = new StateMachine(0);
    stateMachine.moveNextDelegate = stateMachine.MoveNext;
    stateMachine.builder = AsyncTaskMethodBuilder<int>.Create();
    stateMachine.MoveNext();
    return stateMachine.builder.Task;
}

Note that I’ve renamed all the variables and the generated type itself to be valid C# identifiers with useful names.

This is pretty much a minimal stub from an async method. If the async method took parameters, each parameter value would end up being copied into a field in the state machine – but other than that, this is what all async methods end up looking like. (Void async methods just won’t return the builder’s task at the end.)

The method doesn’t have much work to do:

  • It creates a new instance of the state machine, passing in an initial state of 0. (I’d expect this constructor parameter to be removed by release – it’ll always be 0, after all.)
  • It creates a new Action delegate instance corresponding to the MoveNext method in the state machine. This is always passed to awaiters as the continuation – by using a field in the state machine, we can avoid creating a new Action instance each time we call OnCompleted.
  • It creates a new AsyncTaskMethodBuilder which the state machine uses to set the result and/or exception, and which exposes a task we can return to the caller.
  • It calls MoveNext() to synchronously: this will execute at least as far as the first "await" before returning, and may get further than that if the awaited tasks have completed before the await expression.
  • It returns the Task<int> from the builder. By this point there may already be a result, if all the await expressions had already completed. In real async methods, most of the time the returned Task or Task<T> will represent an ongoing task which the state machine will keep track of.

Obviously, all the smarts are in the state machine…

The state machine (completed)

Here’s the complete code of the generated state machine, as of the second CTP of the async feature.

[CompilerGenerated]
private sealed class StateMachine
{
    // Fields representing local variables
    public Task<int> task;

    // Fields representing awaiters
    private TaskAwaiter<int> awaiter;

    // Fields common to all async state machines
    public AsyncTaskMethodBuilder<int> builder;
    private int state;
    public Action moveNextDelegate;

    public StateMachine(int state)
    {
        // Pointless: will always be 0. Expect this to be removed from later builds.
        this.state = state;
    }

    public void MoveNext()
    {
        int result;
        try
        {
#pragma warning disable 0219 // doFinallyBodies is never used
            bool doFinallyBodies = true;
#pragma warning restore
            if (state != 1)
            {
                if (state != -1)
                {
                    task = Task<int>.Factory.StartNew(() => 5);
                    awaiter = task.GetAwaiter();
                    if (awaiter.IsCompleted)
                    {
                        goto Label_GetResult;
                    }
                    state = 1;
                    doFinallyBodies = false;
                    awaiter.OnCompleted(moveNextDelegate);
                }
                return;
            }
            state = 0;
          Label_GetResult:
            int awaitResult = awaiter.GetResult();
            awaiter = new TaskAwaiter<int>();
            result = awaitResult;
        }
        catch (Exception e)
        {
            state = -1;
            builder.SetException(e);
            return;
        }
        state = -1;
        builder.SetResult(result);
    }


    // Obsolete: will be removed from later builds.
#pragma warning disable 0414
    private bool disposing;
#pragma warning restore

    [DebuggerHidden]
    public void Dispose()
    {
        disposing = true;
        MoveNext();
        state = -1;
    }
}

Yes, it’s pretty complicated. Let’s break it down a bit…

Unused code

Some of the code here is pointless. It’s a side-effect of the fact that for the CTP, which uses the same code for iterator blocks and async methods – that’s why the primary method within the state machine is called MoveNext. So everything below the comment line starting "Obsolete" can be ignored.

Additionally, in this case we don’t have any finally blocks – so the doFinallyBodies local variable is unused too. Later on we’ll see an example which does use it, so don’t worry about it for now.

Fields and initialization

I’ve explained all the fields in the comments, to some extent. Broadly speaking, there are three types of field in async method translations:

  • Variables for the infrastructure:
    • The state number, used to work out where to jump back into the method when the continuation is called (or on our first call from the stub method)
    • The builder, used to communicate with the task returned to the caller, via SetResult and SetException
    • The moveNextDelegate, always passed to the OnCompleted method when an awaiter indicates an incomplete task
  • Awaiter variables: each await expression (currently) has its own awaiter variable. (This may be optimized in a later build.) Each one is set back to the type’s default value (e.g. null for a reference type) after use, but we need to maintain this as a field so we can get the result of an awaiter when we return from a continuation.
  • "User" variables representing the local variables of the async method. If the method uses language features such as collection initializers, there may be more of these than expected, but basically they’re whatever you’d see as local variables in a normal method. This also includes parameters for the async method, which are copied into the state machine by the stub method.

Standard skeleton

All async methods (well, those returning results) have a standard skeleton for the MoveNext() method. Void async methods differ in the obvious way. You may recognise this as being extremely similar to part of our manually-written method, too:

public void MoveNext()
{
    int result; // Type varies based on result type
    try
    {
        // Method-specific code
    }
    catch (Exception e)
    {
        state = -1;
        builder.SetException(e);
        return;
    }
    state = -1;
    builder.SetResult(result);
}

Hopefully this doesn’t need much explanation: while catching Exception is normally a bad idea, it’s appropriate here, so that it can be rethrown in the caller’s context (or detected without being rethrown). Obviously the method-specific code in the middle needs to make sure that we only reach the bottom of the method in the case where we’re really done – if it ever calls OnCompleted, it needs to return directly rather than falling through to this code.

Actual method-specific code…

Phew, we’ve finally got to the bit which varies depending on the async method we’re dealing with. Fortunately now that we’ve removed all the extraneous code, it’s easier to see what’s going on. I’ve added some comments for clarity:

  if (state != 1)
  {
      if (state != -1)
      {
          // Code before the first await expression
          task = Task<int>.Factory.StartNew(() => 5);

          // Boiler-plate for the first part of an await
          awaiter = task.GetAwaiter();
          if (awaiter.IsCompleted)
          {
              goto Label_GetResult;
          }
          state = 1;
          doFinallyBodies = false;
          awaiter.OnCompleted(moveNextDelegate);
      }
      return;
  }
  // Boiler-plate for the second part of an await
  state = 0;
Label_GetResult:
  int awaitResult = awaiter.GetResult();
  awaiter = new TaskAwaiter<int>();
                    
  // Code after the await (return, basically)
  result = awaitResult;

Now the state machine for our async method can be in any of three states at the start/end of MoveNext():

  • -1: the method has already completed (possibly with an exception). (It’s unclear how we’d end up getting back into the state machine at this point, but the generated code makes sure it just returns immediately.)
  • 0: The initial state - this is also used just before calling GetResult() for situations where we’ve not needed to use a continuation. I’ll go into this slight oddity in another post :)
  • 1: We’re waiting to be called back by the awaiter. When the continuation is executed, we want to jump straight to "Label_GetResult" so we can fetch the result and continue with the rest of the method.

I suggest you look at the code path taken by the above code if you start in each of those states – convince yourself that when we exit the method, we’ll be in an appropriate state, whether it’s normally or via a continuation.

Even within this code, we can see boilerplate material: any time you reach an await expression in an async method, the compiler will generate code like the middle section, to get the awaiter, then either skip to the "get result" part or add a continuation. The fiddly bit is really getting your head round how the code flow works, but hopefully as there are relatively few states in play here, it’s not too bad.

Conclusion

This was a very long post for such a short async method. However, now that we’ve sorted out how it all hangs together, we should be able to look at more complicated async methods without looking at things like the obsolete code and the stub method. Next time we’ll see what happens when we introduce loops, catch blocks and finally blocks.

Eduasync part 6: using the infrastructure manually

Now that we’ve got the infrastructure for both returning a task from an async method, and awaiting a Task<T>, we’re going to look at what the compiler does for us. I always find that the best way of appreciating a feature like this is to consider what we’d have to do without it. Later on we’re going to see what the C# compiler does in terms of decompiling the actual generated code, but first we’ll look at what we might do to achieve the same end result, using the same infrastructure.

Of course, if we were doing all of this manually we might not choose to use the same infrastructure to start with, but I’m not sure that it would end up being much cleaner anyway.

A simple async method

First, let’s look at the C# 5 async method we want to emulate. It’s pretty simple:

private static async Task<int> ReturnValueAsyncWithAssistance()
{
    Task<int> task = Task<int>.Factory.StartNew(() => 5);

    return await task;
}

Now obviously we could just return the task here to start with – the async method isn’t providing any real benefit. But the simplicity is helpful for showing an equivalent. You can always imagine that there’s more going on between awaiting the result of the task and returning our actual result.

What do we need to do?

Before I include the actual code, let’s consider what we know about the async method execution model:

  • We’ll create an AsyncTaskMethodBuilder to allow us to return a Task<int>.
  • The method will start synchronously, running all the code before the first (and in this case only) await expression in the normal way.
  • The awaitable expression will be evaluated, and GetAwaiter() called to get a suitable awaiter value.
  • We’ll ask the awaiter whether the awaitable has already completed, using the IsCompleted property
    • If it’s already completed, we get the result by calling GetResult(). We can set the result on the builder, and return the builder’s task.
    • Otherwise, create a continuation delegate representing "the rest of the method". We then return the builder’s task.
      • The continuation has to fetch the result by calling GetResult() on the same awaiter, set the result on the builder, and return. (It’s not returning a Task<T> – the continuation is just an Action.)
  • If an exception occurs at any point, whether in the original synchronous call or the continuation, we need to set that exception in the builder, and return (either returning the builder’s task or nothing, depending on the context).

Even describing it makes it sound significantly worse than the async method. Let’s take a look.

Bring on the code!

Here’s the manual method in all its glory:

private static Task<int> ReturnValueAsyncWithoutAssistance()
{
    AsyncTaskMethodBuilder<int> builder = AsyncTaskMethodBuilder<int>.Create();

    try
    {
        Task<int> task = Task<int>.Factory.StartNew(() => 5);

        TaskAwaiter<int> awaiter = task.GetAwaiter();
        if (!awaiter.IsCompleted)
        {
            // Result wasn’t available. Add a continuation, and return the builder.
            awaiter.OnCompleted(() =>
            {
                try
                {
                    builder.SetResult(awaiter.GetResult());
                }
                catch (Exception e)
                {
                    builder.SetException(e);
                }
            });
            return builder.Task;
        }

        // Result was already available: proceed synchronously
        builder.SetResult(awaiter.GetResult());
    }
    catch (Exception e)
    {
        builder.SetException(e);
    }
    return builder.Task;
}

Ugly, isn’t it? Even so, it’s actually somewhere simpler than the real autogenerated code, which we’ll see in the next part. Hopefully if you read the code and comments bearing in mind the bullet points above, it should be reasonably easy to see how it works. However:

  • Currently we’re building a continuation delegate specific to this particular await. Imagine if we had several await expressions. Each would need "the rest of the method" specified as its own delegate, and we’d end up building multiple delegate instances over time.
  • It would be a nightmare to handle loops in this way.
  • We’ve got repeated code – not a lot in this case, but anything we had between the "await" and the "return" would need to be in both flows.
  • You don’t really want to be writing this code by hand anyway, if you can help it.

Having said all of that, it works. Just don’t expect me to write any more complicated async methods this way :)

Conclusion

The steps given here are still going to be valid with the compiler-generated code, but they’ll take a slightly different form. Next time we’ll decompile the generated code for the exact same async method, before we move on to look at more complicated cases.

Eduasync part 5: making Task<T> awaitable

In part 3 we looked at what the C# 5 compiler required for you to "await" something. The sample used a class which actually had an instance method called GetAwaiter, but I mentioned that it could also be an extension method.

In this post, we’ll use that ability to make Task<T> awaitable – at which point we have everything we need to actually see some asynchronous behaviour. Just like the last part, the code here is pretty plain – but by the end of the post we’ll have a full demonstration of asynchrony. I should make it clear that this isn’t absolutely everything we’ll want, but it’s a good start.

TaskAwaiter<T>

As it happens, I’m using the same type name as the async CTP does for "something which can await a task" – TaskAwaiter. It’s in a different namespace though, and we could rename it with no ill-effects (unlike AsyncTaskMethodBuilder, for example). Indeed, in an old version of similar code I called this type Garçon – so GetAwaiter would return a waiter, so to speak. (Yes, you can use non-ASCII characters in identifiers in C#. I wouldn’t really advise it though.)

All the task awaiter needs is a reference to the task that it’s awaiting – it doesn’t need to know anything about the async method which is waiting for it, for example. Task<T> provides everything we need: a property to find out whether it’s completed, another to fetch the result, and the ContinueWith method to specify a continuation. The last part is particularly important – without that, we’d really have a hard time implementing this efficiently.

The extension method on Task<T> is trivial:

public static class TaskExtensions
{
    public static TaskAwaiter<T> GetAwaiter<T>(this Task<T> task)
    {
        return new TaskAwaiter<T>(task);
    }
}

The TaskAwaiter<T> type itself is slightly less so, but only slightly:

public struct TaskAwaiter<T>
{
    private readonly Task<T> task;

    internal TaskAwaiter(Task<T> task)
    {
        this.task = task;
    }

    public bool IsCompleted { get { return task.IsCompleted; } }

    public void OnCompleted(Action action)
    {
        SynchronizationContext context = SynchronizationContext.Current;
        TaskScheduler scheduler = context == null ? TaskScheduler.Current
            : TaskScheduler.FromCurrentSynchronizationContext();
        task.ContinueWith(ignored => action(), scheduler);
    }

    public T GetResult()
    {
        return task.Result;
    }
}

IsCompleted is obviously trivial – Task<T> provides us exactly what we need to know. It’s just worth noting that IsCompleted will return true if the task is cancelled, faulted or completed normally – it’s not the same as checking for success. However, it represents exactly what we want to know here.

OnCompleted has two very small aspects of interest:

  • ContinueWith takes an Action<Task<T>> or an Action<Task>, not just an Action. That means we have to create a new delegate to wrap the original continuation. I can’t currently think of any way round this with the current specification, but it’s slightly annoying. If the compiler could work with an OnCompleted(Action<object>) method then we could pass that into Task<T>.ContinueWith due to contravariance of Action<T>. The compiler could generate an appropriate MoveNext(object) method which just called MoveNext() and stash an Action<object> field instead of an Action field… and do so only if the async method actually required it. I’ll email the team with this as a suggestion – they’ve made other changes with performance in mind, so this is a possibility. Other alternatives:
    • In .NET 5, Task<T> could have ContinueWith overloads accepting Action as a continuation. That would be simpler from the language perspective, but the overload list would become pretty huge.
    • I would expect Task<T> to have a "real" GetAwaiter method in .NET 5 rather than the extension method; it could quite easily just return "this", possibly with some explicitly implemented IAwaiter<T> interface to avoid polluting the normal API. That could then handle the situation more natively.
  • We’re using the current synchronization context if there is one to schedule the new task. This is the bit that lets continuations keep going on the UI thread for WPF and WinForms apps. If there isn’t a synchronization context, we just use the current scheduler. For months this was incorrect in Eduasync; I was using TaskScheduler.Current in all cases. It’s a subtle difference which has a huge effect on correctness; apologies for the previous inaccuracy. Even the current code is a lot cruder than it could be, but it should be better than it was…

GetResult looks and is utterly trivial – it works fine for success cases, but it doesn’t do what we really want if the task has been faulted or cancelled. We’ll improve it in a later part.

Let’s see it in action!

Between this and the AsyncTaskMethodBuilder we wrote last time, we’re ready to see an end-to-end asynchronous method demo. Here’s the full code – it’s not as trivial as it might be, as I’ve included some diagnostics so we can see what’s going on:

internal class Program
{
    private static readonly DateTimeOffset StartTime = DateTimeOffset.UtcNow;

    private static void Main(string[] args)
    {
        Log("In Main, before SumAsync call");
        Task<int> task = SumAsync();
        Log("In Main, after SumAsync returned");

        int result = task.Result;
        Log("Final result: " + result);
    }

    private static async Task<int> SumAsync()
    {
        Task<int> task1 = Task.Factory.StartNew(() => { Thread.Sleep(500); return 10; });
        Task<int> task2 = Task.Factory.StartNew(() => { Thread.Sleep(750); return 5; });

        Log("In SumAsync, before awaits");
           
        int value1 = await task1;
        int value2 = await task2;

        Log("In SumAsync, after awaits");

        return value1 + value2;
    }

    private static void Log(string text)
    {
        Console.WriteLine("Thread={0}. Time={1}ms. Message={2}",
                          Thread.CurrentThread.ManagedThreadId,
                          (long)(DateTimeOffset.UtcNow – StartTime).TotalMilliseconds,
                          text);
    }
}

And here’s the result of one run:

Thread=1. Time=12ms. Message=In Main, before SumAsync call
Thread=1. Time=51ms. Message=In SumAsync, before awaits
Thread=1. Time=55ms. Message=In Main, after SumAsync returned
Thread=4. Time=802ms. Message=In SumAsync, after awaits
Thread=1. Time=802ms. Message=Final result: 15

So what’s going on?

  • We initially log before we even start the async method. We can see that the thread running Main has ID 1.
  • Within SumAsync, we start two tasks using Task.Factory.StartNew. Each task just has to sleep for a bit, then return a value. Everything’s hard-coded.
  • We log before we await anything: this occurs still on thread 1, because async methods run synchronously at least as far as the first await.
  • We hit the first await, and because the first task hasn’t completed yet, we register a continuation on it, and immediately return to Main.
  • We log that we’re in Main, still in thread 1.
  • When the first await completes, a thread from the thread pool will execute the continuation. (This may well be the thread which executed the first task; I don’t know the behaviour of the task scheduler used in console apps off the top of my head.) This will then hit the second await, which also won’t have finished – so the first continuation completes, having registered a second continuation, this time on the second task. If we changed the Sleep calls within the tasks, we could observe this second await actually not needing to wait for anything.
  • When the second continuation fires, we log that fact. Two things to notice:
    • It’s almost exactly 750ms after the earlier log messages. That proves that the two tasks has genuinely been executing in parallel.
    • It’s on thread 4.
  • The final log statement occurs immediately after we return from the async method – thread 1 has been blocked on the task.Result property fetch, but when the async method completes, it unblocks and shows the result.

I think you’ll agree that for the very small amount of code we’ve had to write, this is pretty nifty.

Conclusion

We’ve now implemented enough of the functionality which is usually in AsyncCtpLibrary.dll to investigate what the compiler’s really doing for us. Next time I’ll include a program showing one option for using the same types within hand-written code… and point out how nasty it is. Then for the next few parts, we’ll look at what the C# 5 compiler does when we let it loose on code like the above… and show why I didn’t just have "int value = await task1 + await task2;" in the sample program.

If you’ve skimmed through this post reasonably quickly, now would be a good time to go back and make sure you’re really comfortable with where in this sample our AsyncTaskMethodBuilder is being used, and where TaskAwaiter is being used. We’ve got Task<T> as the core type at both boundaries, but that’s slightly coincidental – the boundaries are still very different, and it’s worth making sure you understand them before you try to wrap your head round the compiler-generated code.

Eduasync part 4: Filling in AsyncTaskMethodBuilder<T>

In part 2, I introduced AsyncVoidMethodBuilder, AsyncTaskMethodBuilder and AsyncTaskMethodBuilder<T>. I showed all the signatures, but the implementations were basically trivial and non-functional. Today, I’m going to change all of that… at least a bit.

In particular, by the end of this post we’ll be able to make the following code actually print out 10 as we’d expect:

private static void Main(string[] args)
{
    Task<int> task = Return10Async();
    Console.WriteLine(task.Result);
}

private static async Task<int> Return10Async()
{
    return 10;
}

Note that there are no await statements – remember that AsyncTaskMethodBuilder<T> is about the boundary between the caller and the async method. We won’t be actually doing anything genuinely asynchronous today – just filling in a piece of the infrastructure.

I’m not going to implement AsyncVoidMethodBuilder or AsyncTaskMethodBuilder, because once you understand how AsyncTaskMethodBuilder<T> works, the others are basically trivial to implement. (Certainly the non-generic AsyncTaskMethodBuilder is too similar to warrant going into; I may potentially revisit AsyncVoidMethodBuilder to investigate its behaviour on exceptions.)

Just as a reminder, this is what we need our AsyncTaskMethodBuilder<T> struct to look like in terms of its interface:

public struct AsyncTaskMethodBuilder<T>
{
    public static AsyncTaskMethodBuilder<T> Create();
    public void SetException(Exception e);
    public void SetResult(T result);
    public Task<T> Task { get; }
}

Frankly, this would be a bit of a pain to implement ourselves… especially if we wanted to do it in a neat way which didn’t introduce a new thread unnecessarily. Fortunately, Parallel Extensions makes it pretty easy.

TaskCompletionSource to the rescue!

The TaskCompletionSource<TResult> type in the framework makes it almost trivial to implement AsyncTaskMethodBuilder<T>. It provides us everything we need – a task, and the ability to specify how it completes, either with an exception or a value. Our initial implementation is really just going to wrap TaskCompletionSource. I suspect the real CTP does slightly more work, but you can get a remarkably long way with simple wrapping:

public struct AsyncTaskMethodBuilder<T>
{
    private readonly TaskCompletionSource<T> source;

    private AsyncTaskMethodBuilder(TaskCompletionSource<T> source)
    {
        this.source = source;
    }

    public static AsyncTaskMethodBuilder<T> Create()
    {
        return new AsyncTaskMethodBuilder<T>(new TaskCompletionSource<T>());
    }

    public void SetException(Exception e)
    {
        source.SetException(e);
    }

    public void SetResult(T result)
    {
        source.SetResult(result);
    }

    public Task<T> Task { get { return source.Task; } }
}

We’ll see how far that takes us – we may need to tweak it a bit, probably around the exception handling. For the moment though, it certainly does everything we need it to. The program runs, and prints out 10 as we’d expect.

This part of the code is now ready for asynchrony – even though nothing we’ve done so far actually creates any different threads. If we call SetResult from one thread while another thread is waiting on the task’s result, the waiting thread will be unblocked as we’d expect.

Conclusion

In some ways I hope you’re disappointed by this, in the same way that looking at the LINQ to Objects implementations can be slightly disappointing. It feels like so much is being given to us for free – the framework already had all this power, so what’s C# 5 really adding to the mix. Well, we’re nearly at the point where I can show you the details of what the compiler’s doing under the hood. That’s where the real power lies, and is why I’m so excited by the feature.

First though, we need to implement the awaiter pattern for Task<T>. That’s the task of our next post, and then we can complete the picture by decompiling the generated code for some programs using genuine asynchrony.

Eduasync part 3: the shape of the async method / awaitable boundary

Last time we looked into the boundary between the caller of an async method and the method itself.

This time I’m going to show the same sort of "skeleton API" but for awaitable types. This is at the heart of C# 5’s async feature: within an async method, you can include "await" expressions. You have to await a value – which could be the return value from a method call, or a property, or the value of a variable. The important thing is that the compile-time type of that expression has the right shape.

Definition time

Consider this code snippet from an async method (separated into two statements for clarity):

Task<string> t = new WebClient().DownloadStringTaskAsync(url);
string text = await t;

I wish to define the following terms:

  • The task of the await expression is the expression which comes after "await"; in this case it’s just "t".
  • The awaitable type is the compile-time type of the task. In this case it’s Task<string>.
  • The awaiter type is the compile-time type of the result of calling GetAwaiter() on the task.
  • The result type is the compile-time return type of the awaiter type’s GetResult method. Here it would be string, when using the CTP or any sensible implementation.

(Of these, "task" and "awaiter type" are part of the draft specification. "Awaitable type" and "result type" aren’t.)

Now, the first two are fairly clear from the code above, but the awaiter and result types aren’t. The compiler validates that calling (foo).GetAwaiter() for a task expression "foo" is valid. It doesn’t have to be an instance method – it can be an extension method, and indeed that’s how it’s implemented in the CTP. Unlike the weird things you can do with LINQ query expressions, GetAwaiter() really does have to be a method call, and the expression can’t just be a type name. Unless you’re trying to do weird stuff, this is very unlikely to be an issue for you. It’s only oddballs like me who try to push the envelope.

With the current CTP the awaitable type can’t be "dynamic", but the aim is for C# 5 to support awaiting dynamic values. I imagine this won’t affect the execution flow significantly though.

The awaitable type only has to support the GetAwaiter() method, as we’ve said. The awaiter type has to have the following accessible members (which can’t be extension methods):

  • bool IsCompleted { get; }
  • void OnCompleted(Action continuation)
  • X GetResult() where X defines the result type for the awaiter, and may be void

The overall type of the await expression is the result type – and it has to be appropriate for any surrounding context. For example, the assignment to the "text" variable will only work in our sample if the result type is "string" or a type with an implicit conversion to string.

A simple (non-functional) awaiter

The sample code for this post comes from project 5 in the source solution (SimpleInt32Awaitable); project 4 is similar but with a void result type. Neither project makes any attempt to do any real awaiting:

// Within some other class
static async void SimpleWaitAsync()
{
    SimpleInt32Awaitable awaitable = new SimpleInt32Awaitable();
    int result = await awaitable;
}
 


public struct SimpleInt32Awaitable
{
    public SimpleInt32Awaiter GetAwaiter()
    {
        return new SimpleInt32Awaiter();
    }
}

public struct SimpleInt32Awaiter
{
    public bool IsCompleted { get { return true; } }

    public void OnCompleted(Action continuation)
    {
    }

    public int GetResult()
    {
        return 5;
    }
}

Note that here both the awaitable type and the awaiter type are structs rather than classes; this is not a requirement by any means.

What does it all mean?

I don’t want to leave this post without any clue of what all of this is used for.

Fairly obviously, GetAwaiter() is called to get an awaiter (clever name, huh?) on which some members are called:

  • IsCompleted is called to determine whether the task has already completed. If it has, we can skip over the next step.
  • If the task hasn’t already completed, we want to return very soon rather than waiting – so OnCompleted is calling with a delegate representing the continuation of the method. The awaiter promises to ensure that the continuation is called when (and only when) the task has completed, possibly with an exception.
  • When we know the task has completed (either synchronously or via the continuation), GetResult is called to retrieve the result. Note that even if the method has a void return type, it still has to be called, as the "result" may be that an exception is thrown.

Conclusion

Awaiters are pretty flexible – which means they can be abused in evil, horrible ways, as we’ll see later on.

So far we’ve seen the basic shape of both boundaries, and now we can start actually implementing them with real, useful code.

Eduasync part 2: the shape of the caller / async method boundary

For the first few parts of this blog series, we’re not going to write code which actually does anything useful. We’re looking at the API more than the implementation – the bare necessities to get the code to compile. Remember that we’re working without AsyncCtpLibrary.dll – which of course supplies all the necessary types normally.


In this part, we’ll look at the boundary between the caller and the async method itself. Our test code will have a method which does nothing but return – but which is marked with the “async” modifier. The fact that we don’t have an “await” expression in the method causes a compiler warning – which is entirely reasonable, but irrelevant to our particular situation.

Three return types, one basic idea

Async methods are limited to three different return types:

  • void
  • Task (non-generic)
  • Task<T> (generic for any type T)

Each of these return types needs a very slightly different form of compiler support… but the general principle is the same. I’ll show each of the three different types, but the test code itself remains exactly the same except for the return type of the method and the return statement. (If you’re going to return Task<int>, you need to return an int, etc.)

namespace Eduasync
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            DoNothingAsync();
        }

// Warning CS1998 is about a method with no awaits in… exactly what we’re trying to
// achieve!
#pragma warning disable 1998 
        // Return type of void, Task or Task<int>
        private static async void DoNothingAsync()
        {
            // For Task<int> insert return 0; here
        }
#pragma warning restore 1998
    }
}


If you try to compile this code without anything else, the compiler gives an error like this:

Test.cs(14,30): error CS0656: Missing compiler required member ‘System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Create’
Test.cs(14,30): error CS1993: Cannot find Task-related types. Are you missing a reference to ‘AsyncCtpLibrary.dll’ ?


While you’d get a similar error for the async method / awaitable boundary, this one is more demanding: you have to have exactly the right types available. It isn’t just a matter of any library providing support for your current situation (in the same way that Edulinq can live in its own namespace and be an alternative for LINQ to Objects without the compiler caring). These are exact types that the compiler relies on. They’re an implementation detail: they can vary between compilers (so Mono could have a different set of types for example, although I doubt that it will, for the sake of binary compatibility) and they’re not part of the language specification… at least at the moment.

There are three types required: AsyncVoidMethodBuilder, AsyncTaskMethodBuilder and AsyncTaskMethodBuilder<T>. They correspond (fairly obviously) to the return types of void, Task, and Task<T> respectively.

They all look largely the same, but here are my initial "non-implementations":

using System.Threading.Tasks;

namespace System.Runtime.CompilerServices
{
    public struct AsyncVoidMethodBuilder
    {
        public static AsyncVoidMethodBuilder Create()
        {
            return new AsyncVoidMethodBuilder();
        }

        public void SetException(Exception e) {}
        public void SetResult() {}
    }

    public struct AsyncTaskMethodBuilder
    {
        public static AsyncTaskMethodBuilder Create()
        {
            return new AsyncTaskMethodBuilder();
        }

        public void SetException(Exception e) {}
        public void SetResult() {}
        public Task Task { get { return null; } }
    }

    public struct AsyncTaskMethodBuilder<T>
    {
        public static AsyncTaskMethodBuilder<T> Create()
        {
            return new AsyncTaskMethodBuilder<T>();
        }

        public void SetException(Exception e) {}
        public void SetResult(T result) {}
        public Task<T> Task { get { return null; } }
    }
}

(In the Eduasync project these are in separate files and have diagnostic statements in the SetException/SetResult methods. I didn’t want to take up too much space here.)

Note that these must be structs. If they’re not, the compiler will complain. All the methods have to have the exact signatures specified, as far as I can tell – except that everything can be internal if you want it to be (so long as your async methods are in the same assembly of course). In practice these are going to be public, and they are public everywhere in Eduasync.

With these implementations, you can compile and even run async methods – but you will, of course, end up with a null reference returned from any async method with a return type of Task or Task<T>.

In case you’re wondering, the code below shows a little taste of how these types are used. It’s from the version of DoNothingAsync which returns a non-generic Task. This is what the compiler replaces our code with:

private static Task DoNothingAsync()

    <DoNothingAsync>d__0 d__ = new <DoNothingAsync>d__0(0);
    d__.<>t__MoveNextDelegate = new Action(d__.MoveNext);
    d__.$builder = AsyncTaskMethodBuilder.Create();
    d__.MoveNext(); 
    return d__.$builder.Task; 
}

Obviously <DoNothingAsync>d__0 is a compiler-generated type (hence the unspeakable name). I’m not going into the details just yet – that will the topic of several posts in a little while. The main point of this post was just to show you the shape of what the compiler requires. The Create() method and the Task property are used within the rewritten method; the SetResult() and SetException() methods are used within the generated type (the state machine) to indicate the async method completing.

Before too long we’ll implement these types properly (which is reasonably straightforward, given help from the BCL).

Conclusion

The caller / async method boundary is relatively inflexible. The compiler relies on particular types, and you simply can’t make an async method return a different kind of value, such as your own Future<T> type. The guts of how it works are all hidden from you, unless you try to compile without the right libraries around: while the valid return types are part of the specification, the types used by the compiler aren’t. While this is perhaps a little unfortunate in a purist sense, it’s not really a big deal. The "consuming" part of the async method (the boundary between the async method and whatever it’s awaiting) is much more flexible, and more interesting. That’s what we’re going to look at next.

Eduasync part 1: introduction

I’ve been waiting to start this blog series for a couple of months. It’s nice to finally get cracking.

Hopefully some of you have already read some of my thoughts around C# 5’s async feature, mostly written last year. Since that initial flurry of posts, I’ve been pretty quiet, but I’m still really excited about it. Really, really excited. I’ve given a few talks on it, and I have a few more still to give – and this blog series will partly be complementary to those talks. In particular, there’s a DevExpress webcast which covers most of the same ground, with similar code. (It was before the CTP refresh, and also before my laptop was stolen in a burglary, so the code here is a rewrite.)

Async from a compiler’s point of view

Most of this blog series (at least the bits I anticipate at the moment) will deal with what the compiler does with async methods. (I haven’t used async delegates much at all, but I can’t imagine that the machinery is particularly different.)

As far as I’ve seen, most of the coverage on the web so far has dealt with using async. That’s natural, logical and entirely proper. Oh, and a bit boring after a while. I like knowing how a feature works before I go too far using it. This is a personal idiosyncrasy, and if you’re happy just using async with no “under the hood” details, that’s absolutely fine. It’s probably worth unsubscribing from my blog for a little while, that’s all.

This can all be seen as pretty similar to my Edulinq series of posts, which is why I’ve called it Eduasync this time.

My plan is to walk you through what the C# compiler relies on – the types which are currently part of AsyncCtpLibrary.dll, and how it interacts with Task / Task<T> from .NET 4. We’ll then look at the code generated by the compiler – essentially a state machine – and some of the less obvious aspects of it. I’ll give examples of any bugs I’ve found in the CTP, just for the heck of it – and as a way of checking whether they’re fixed in later versions. (Obviously I’ve let the C#/VB team know about these as I’ve come across them.)

I’ll assume that you know the basics of using async – so if you don’t, now would be a good time to look at the numerous resources on the Visual Studio Async home page. There are loads of videos, specs (including the C# spec changes, most importantly from my point of view)

Get the source now

There’s already quite a bit of source code (everything I’m currently planning on writing about, which is almost inevitably less than I’ll actually end up writing about) on the Google Code Eduasync project. This takes a different approach from Edulinq – instead of just a couple of projects (production and tests, basically) I’ve got a separate project for each topic I want to talk about, with pretty minimal code for that topic. The reason for this is to show the evolution of the code – starting off with almost nothing, and progressing until we’ve got an implementation which achieves at least the bare bones important bits of an async system.

I’ve numbered the projects within the solution, although the assemblies themselves don’t have the same numbers. They all use a default namespace of just Eduasync, and they don’t refer to each other. Each is meant to be self-contained – oh, and there are no references to AsyncCtpLibrary.dll. The whole point is to reimplement that library :) Of course, you’ll still need the CTP installed to get the compiler changes.

The Google Code repository will also contain the blog posts eventually, including any diagrams I need to create (such as the one in a minute).

The three blocks and two boundaries

One of the things I’ve found important to think about in async is the various parts involved. I’ve ended up with a mental model like this:

The bits in blue and red are the ones we’re focusing on here: the contents of the async method, and the boundaries between that and the code that calls it, and the tasks (or other awaitable types) that it awaits.

For most of this series we’re not really going to care much about what the caller does with the result, or how the awaitable object behaves other than in terms of the methods and properties used by the C# 5 compiler. I’ll discuss the flexibility afforded though – and how it doesn’t extend to the "caller/async" boundary, only the "async/awaitable" boundary.

Just to give an explicit example of all of this, here’s a simple little program to asynchronously determine the size of the Stack Overflow home page:

using System;
using System.Net;
using System.Threading.Tasks;

class Program
{
    // Caller (block 1)
    static void Main()
    {
        Task<int> sizeTask = DownloadSizeAsync("http://stackoverflow.com");
        Console.WriteLine("In Main, after async method call…");        
        Console.WriteLine("Size: {0}", sizeTask.Result);
    }
    
    // Async method (block 2)
    static async Task<int> DownloadSizeAsync(string url)
    {
        var client = new WebClient();
        // Awaitable (block 3)
        var awaitable = client.DownloadDataTaskAsync(url);
        
        Console.WriteLine("Starting await…");
        byte[] data = await awaitable;
        Console.WriteLine("Finished awaiting…");
        
        return data.Length;
    }
}

The comments should make it reasonably clear what the blocks in the diagram mean. It’s not ideal in that the first two blocks are basically methods, whereas the third block is an object – but I’ve found that it still makes sense when we’re thinking about the interactions involved at the boundaries. Notably:

  • How does the async method create an appropriate value to return to the caller?
  • How does the async method interact with the awaitable when it hits an "await" expression?

We can (and we’re going to) look at these boundaries very separately. We’ll start off with the first bullet, in part two, which will hopefully follow in the next few days.