async/await Doesn’t Make Synchronous Code Asynchronous

, ,

With the recent release of .Net 4.5/C# 5, I’ve spent time experimenting with the new async/await functionality. To my surprise, these new keywords didn’t have the effect I anticipated. Based on my skimming of pre-release information, I was under the impression that the appropriate use of these keywords would cause a normally-synchronous method to execute asynchronously.

Wrong!

async/await do not make synchronous code asynchronous. Instead, these keywords make it much easier to code continuations, eliminating ugly boilerplate code.

My Expectation

I thought that awaiting Run’s call to DoWork would cause DoWork to be executed asynchronously, resulting in “After call to DoWork” being outputted before “DoWork Complete.”

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

namespace AsyncAwaitExperiment
{
    class Program
    {
        static void Main(string[] args)
        {
            Run();
            Console.WriteLine("After call to DoWork");
            Console.ReadKey();
        }

        async static Task Run()
        {
            await DoWork();
        }
        async static Task DoWork()
        {
            Console.WriteLine("DoWork Starting");

            // keep the processor busy for a while
            for (int i = 0; i < 1000000000; i++) { var total = i * i; }

            Console.WriteLine("DoWork Complete");
        }
    }
}

Nope! In the above code, the only effect of async/await is to generate a warning pertaining to DoWork: “This async method lacks ‘await’ operators and will run synchronously. Consider using the ‘await’ operator to await non-blocking API calls, or ‘await Task.Run(…)’ to do CPU-bound work on a background thread.”

If async/await do not make synchronous code asynchronous, what’s the point of this new language feature?

A Continuation

Change DoWork so that the math computations execute on the thread pool, then lean back and watch async/await work.

        async static Task DoWork()
        {
            Console.WriteLine("DoWork Starting");
            await Task.Run(() =>
            {
                // keep the processor busy for a while
                for (int i = 0; i < 1000000000; i++) { var total = i * i; }
            });
            Console.WriteLine("DoWork Complete");
        }

In this revised example, after DoWork’s await Task.Run statement is executed, the flow of control passes back to Main instead of waiting for the just-created task to complete. Then when the task completes, the code in DoWork following await Task.Run is executed.

In effect, async/await has taken the code in DoWork after the awaited task and attached it to that task as a continuation. When the task completes, the rest of the code in the method is executed.

To see this in action, run the revised example. First, “DoWork Starting” is outputted, immediately followed by “After call to DoWork” (outputted from inside Main). Then, after a few moments, “DoWork Complete” appears (the continuation is executed).

2 thoughts on “async/await Doesn’t Make Synchronous Code Asynchronous

  1. Roberto Prevato

    Ben, thank You for this post!
    It’s extremely clear and helped me understanding what I want to achieve.

    Best Regards

    Reply
  2. Brian

    THANK YOU.

    The Syntax of async / await is a bit confusing.

    async only STATES that this function COULD fork into a separate thread. But this only occurs if it contains an await ! …. in which case it immediately returns execution to the caller while it awaits that process or processes on a separate thread.

    Without an await, the function just runs synchronously as far as the caller is concerned.

    This is really quite odd. I’d think that async func would already run in a separate thread, and if you want to wait for it, you’d call await if you want to WAIT for it instead of letting it run asynchronously.

    To wait for an async call you have to grab the task, hope it returns something ( Task not just Task) and then call task.Result to grab the result within your thread. Waiting for the result thus causes it to run synchronously to on your thread.

    Without a Task, you’d define an intermediary async Task MyWaiter() { await TrueAsyncFunc(); return 0; } and then call var zero = MyWaiter().Result;

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *