March 13th, 2013

“Invoke the method with await”… ugh!

Stephen Toub - MSFT
Partner Software Engineer

I can be a bit sensitive when it comes to language and how concepts are conveyed.  I think it’s important to be accurate, even if not precise, when describing what something is or how to use it, as otherwise the folks to whom you’re communicating can easily form the wrong mental model for that thing.  Having a good mental model for something is important in then being able to reason about the thing in question and to correctly infer other uses and behaviors.

That’s why I frequently cringe when I hear someone say something like “invoke the method using ‘await’”.  The communicator is usually trying to convey that a statement like:

FooAsync();

should instead be:

await FooAsync();

with the await keyword prefixed to the method invocation.  Now, maybe that statement of “invoke the method using ‘await’” is sufficient to communicate to the audience the desired result.  So why do I cringe? Because the statement doesn’t make any sense!

The ‘await’ keyword has nothing to do with invoking methods in this regard… nothing.  It doesn’t influence how a method is invoked, nor is it somehow associated by the compiler with a subsequent method call.  It’d be like someone showing the code:

return Foo();

and saying “I’m using the ‘return’ keyword to invoke the method”.  In both this case and in the ‘await’ case, the keyword isn’t operating on the method or somehow influencing the method’s invocation.  Rather, it’s operating on the result of the method’s invocation.  The statement:

await FooAsync();

is functionally identical to:

var t = FooAsync();
await t;

just as:

return Foo();

is functionally identical to:

var t = Foo();
return t;

If you hear someone say “invoke the method using ‘await’”, that might lead you to believe that ‘await’ does in fact somehow relate to method invocation, which might in turn shield you from the fact that you can await anything that’s awaitable, and conversely that you can invoke an async method without awaiting its result.  There are a whole host of valuable scenarios that come about from doing such things.  For example, the ability to launch multiple asynchronous operations to run concurrently and then wait for them all to complete relies on the ability to not immediately await for an operation after launching it, e.g.

await Task.WhenAll(from url in urls select DownloadAsync(url));

I recently came across another such example in my own personal weekend-coding project.  I was working on a Windows Store app** that has play and pause buttons which influence an audio loop implemented with async/await.  Each iteration through the loop, the code will check to see if a pause request has been issued, using a construct very much like that described at https://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx, e.g.

foreach(var sample in decoder.GetSamples())
{
    …
    await pauseToken.WaitWhilePausedAsync();
    …
}

This works, but due to the way the audio system functions, at the point the loop notices there’s a pause request there are already several buffers worth of audio data queued up to the underlying audio system to be played, which means that there is a several second delay between the time a pause is requested and the time music stops playing.  To fix that, I needed to actually stop the underlying audio system while paused, e.g.

foreach(var sample in decoder.GetSamples())
{
    …
    player.Stop();
    await pauseToken.WaitWhilePausedAsync();
    player.Start();
    …
}

There’s a new problem with this, however.  By stopping and starting the audio like this, it can result in audible hiccups in playback, since we’re stopping and starting even if pausing wasn’t requested.  I really only wanted to do this stopping and starting if a pause was requested, so that the only time stoppage is induced is when the user has actually requested them. And for that, it’s very helpful that await isn’t tied to a method’s invocation, because I was able to do something like this:

foreach(var sample in decoder.GetSamples())
{
    …
    var t = pauseToken.WaitWhilePausedAsync();
    if (t.Status != TaskStatus.RanToCompletion)
    {
        player.Stop();
        await t;
        player.Start();
    }
    …
}

In short, once you think about await as operating on awaitables rather than operating on method invocations, you gain a good deal of freedom in how you can write your asynchronous operations.

———————

**Personal plug about the aforementioned app: For anyone interested in karaoke, Vocals Be Gone (http://apps.microsoft.com/windows/en-US/app/vocals-be-gone/783df35d-2d8a-4f0f-bace-56c873ec671c) is a free app for Windows 8 that provides a simple UI for music playback (mp3, wma, and wav), supports background audio, snapped usage, and all such goodness, but most importantly enables attempting to remove the primary vocals from the audio.  It also supports pitch shifting, so that regardless of whether you’re using the vocal removal feature, you can play back the music in a different key than in what it was recorded.  The vocal removal today employs a very simple algorithm, and won’t work well with all recordings (nor will it work with DRM-protected content), so it may not work great with whatever song you pick first.  Try a few professional recordings, enjoy, and let me know what you think.  And as long as you’re downloading apps, you can also download the free Sudoku Classic app (http://apps.microsoft.com/windows/en-us/app/sudoku-classic/c41fec40-7bf8-4a8c-a0fa-53ecd4563596) for Windows 8.  It provides a very nice Sudoku experience (if I do say so myself :-), including a good puzzle generator with a variety of controls over how the puzzles are generated; support for playing with touch, pen, keyboard, or mouse; and a host of other goodies.  Enjoy!

Author

Stephen Toub - MSFT
Partner Software Engineer

Stephen Toub is a developer on the .NET team at Microsoft.

0 comments

Discussion are closed.