When at last you await

When at last you await

Rate This
  • Comments 3

When you start using async methods heavily, you’ll likely see a particular pattern of composition pop up from time to time.  Its structure is typically either of the form:

async Task FooAsync()
{
    … // some initialization code without awaits 
    await BarAsync(…);
}

or of the form:

async Task<T> FooAsync()
{
    … // some initialization code without awaits
    return await BarAsync(…);
}

A concrete example of this might be a FlushAsync method on a custom buffered stream, where the stream maintains a byte[] of data to write out, along with the number of bytes buffered, and flushing the buffer involves resetting the buffered count and writing the data out to the underlying wrapped stream, e.g.

public override async Task FlushAsync()
{
    int numBuffered = m_numBuffered;
    m_numBuffered = 0;
    await m_wrappedStream.WriteAsync(m_buffer, 0, numBuffered);
}

When we find code that follows this particular pattern, we can employ an alternative.  Instead of the above, we could write this FlushAsync method as follows:

public override Task FlushAsync()
{
    int numBuffered = m_numBuffered;
    m_numBuffered = 0;
    return m_wrappedStream.WriteAsync(m_buffer, 0, numBuffered);
}

The key difference here is that this method is no longer using the compiler support for writing async methods.  By removing the “async” keyword, this is just an ordinary method, rather than one that the compiler rewrites into a state machine.  And along with that, we’re now returning a Task created by the body of the method rather than relying on the compiler to create one for us.

Why would we want to do this?  Purely as an optimization.  If the particular code in question is very performance-sensitive, you can save some cycles by avoiding the overhead necessary to support compiler-generated async methods.  When you use the async/await keywords, the compiler and Framework collude to generate a Task on your behalf to represent all of the async method’s processing.  In this case, however, we’re being handed a task to represent the last statement in the method, and thus it’s in effect already a representation of the entire method’s processing (as long as nothing in the preamble could throw an exception, since such an exception would directly escape the method rather than being stored into the returned task).  Since it already provides the representation we need, we can return it untouched to our caller, saving a level of indirection.

For more information on performance and async methods, see the October 2011 issue of MSDN Magazine.

Leave a Comment
  • Please add 4 and 1 and type the answer here:
  • Post
  • Stephen, is there a reason why the compiler can't/doesn't do this optimization?

  • Hi Adam-

    As one example, the compiler doesn't know whether the code before the await might throw an exception.  When you apply the async keyword to a method, all exceptions from within that method will be stored into the returned Task, but with this optimization, any exceptions from within the method will directly escape rather than being wrapped in a task.  Now, that could be addressed by the compiler with additional code gen, e.g. by wrapping the whole body of the method in a try/catch and doing some special fixups, but this starts to get more complicated, and it's no longer the simple transformation highlighted in the post.

  • Good answer, thank you!

Page 1 of 1 (3 items)