When Async/Await Does Not Await

by Larry Spencer Tuesday, October 9, 2012 9:39 PM

Last time, I showed how the async/await pattern normally operates. Now I'll explain the first of two circumstances when it might not behave as you expect. In the code below, I've introduced a call to Sleep on line 34. What a difference it makes in the flow of control!

 

class Program
{
    static void Main(string[] args)
    {
        WriteALot();
        Console.ReadLine();
    }

    static async void WriteALot()
    {
        Task task;
        using (new CodeTimer("Calling WriteFileAsync"))
            task = WriteFileAsync();
        using (new CodeTimer("Awaiting WriteFileAsync"))
            await task;
    }

    static async Task WriteFileAsync()
    {
        using (new CodeTimer("Executing WriteFileAsync"))
        {
            int writesDone = 0;
            int writesDesired = 5;
            var bytes = new byte[102400];
            var tempFile = Path.GetTempFileName();
            using (var strm = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
            {
                for (writesDone = 0; writesDone < writesDesired; ++writesDone)
                {
                    using (new CodeTimer("WriteAsync with its await"))
                    {
                        strm.Seek(0, SeekOrigin.Begin);
                        var task = strm.WriteAsync(bytes, 0, bytes.Length);
                        Thread.Sleep(2000); // THIS IS THE NEW STATEMENT
                        await task;
                    }
                }
            }
        }
    }
}

 

Here's how execution proceeded without the Thread.Sleep (from the last post). Notice that we returned from WriteFileAsync almost immediately -- after only 0.0061 seconds. Only once we were "Awaiting WriteFileAsync" (line 15) did WriteFileAsync's remaining loops get executed.

And here is is with Thread.Sleep. WriteFileAsync completed all its processing before we went back to line 14. Once back there, we did not wait at all ("Finished in 0.0000 seconds."). In other words, the program behaved exactly as an old-fashioned, synchronous one. It did all the loops in WriteFileAsync, returned normally and continued on its way.

Why the difference? By the time we reach the await statement on line 35, we have had two full seconds for strm.WriteAsync (line 33) to complete, and it has completed. There is no need to await, so the await does nothing. We just go on to the next iteration.

In the version from the last post, without the Thread.Sleep, when execution reached the await on line 35, the async/await mechanism said, "We can't do anything more here, so let's queue up the rest of this method as a continuation task, and go back to the caller." The caller then got to use the thread (remember, async/await is single-threaded), until it had to await (line 15). At that point, control went to the continuation task, where the remaining loops were made.

I hope this has further clarified the async/await method. The main points are

  • Async/await is single-threaded.
  • If the task that is awaited has already finished, the await does nothing.

Next time, we'll see what happens when we don't use the special WriteAsync method on line 33, but call a normal Write instead. Can you guess?

Tags: ,

Add comment

About the Author

Larry Spencer

Larry Spencer develops software with the Microsoft .NET Framework for ScerIS, a document-management company in Sudbury, MA.