Archive

Archive for August, 2012

A Boring Discovery!

August 28, 2012 3 comments

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?

Categories: Uncategorized Tags: , ,