Home > Uncategorized > A Boring Discovery!

A Boring Discovery!

I was writing a simple example program to explain C#5’s async/await keywords. To keep it really simple and “old school”, I decided to make it a console app, and to read lines from the console until the user typed quit. First, the synchronous version:

public static List<string> Foo()
{
    var lines = new List<string>();

    string line;
    while ((line = Console.In.ReadLine()) != "quit")
        lines.Add(line);

    return lines;
}

And then the async version:

public static async Task<List<string>> Foo()
{
    var lines = new List<string>();

    string line;
    while ((line = await Console.In.ReadLineAsync()) != "quit")
        lines.Add(line);

    return lines;
}

Really straightforward, right? I just add the async keyword, wrap the return type inside Task<T> and then I can use await Console.In.ReadLineAsync() instead of Console.In.ReadLine().

So I tried this, and gosh-dang-diddly, it didn’t behave at all as expected. In fact, both versions behaved the same. Could it be something exciting to do with how the SynchronizationContext is set up in console apps? Sorry, no. Try to think of something much duller than that.

The answer is… wait for it… ReadLineAsync() isn’t asynchronous at all. It doesn’t return its Task<string> until the whole line has been read.

Why is that? TextReader.ReadLineAsync appears to be properly asynchronous, and Console.In returns a TextReader as you’d expect… but not quite. It first passes it to TextReader.Synchronized, and guess how the resulting wrapper class implements ReadLineAsync? Thanks to .NET Reflector, we don’t have to guess:

public override Task<string> ReadLineAsync()
{
    return Task.FromResult<string>(this.ReadLine());
}

Yep, it calls ordinary ReadLine, totally synchronously, and then makes an already-finished Task<string>. In real life this is probably harmless because it’s such an edge case, but you can imagine the tedious diversion it caused in the middle of the explanation I was giving.

To get around it, I used this alternative helper, to move the ReadLine to the thread pool:

public static Task<string> ReadConsoleAsync()
{
    return Task.Run(() => Console.ReadLine());
}

Moral: I have no idea. How about “Sometimes, things don’t exactly work.” Is that a moral?

Advertisements
Categories: Uncategorized Tags: , ,
  1. Allon Guralnek
    February 12, 2013 at 8:02 pm

    Did you open a Connect bug report?

    • earwicker
      February 13, 2013 at 3:26 pm

      I can’t see how there’s any point as they can’t do much about it – now it’s out in the wild like this, changing it might break existing applications.

      • Allon Guralnek
        February 13, 2013 at 9:25 pm

        They fix bugs that cause changes all the time. Just upgrading from .NET 4.0 to 4.5 fixed a plethora of bugs, for some of which I used workarounds to overcome. The fixes caused the workarounds to turn into bugs themselves! Let them decide if they’re going to fix it or not.

        Even if they don’t fix it, they can document it. Even if they don’t document it, the connect report itself is a form of documentation indexed by Google which can be found by others. Even if it’s never read by anyone, you’d have done your due diligence indicative of a good dotnetizen.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: