Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Awaitable Console Application

5.00/5 (1 vote)
1 May 2014CPOL2 min read 17.3K  
Awaitable console application

The other day, someone posted some code about how to make Async/Await easier to work with. What they went for was not correct, and looked more like RXs model of OnNext/OnError/OnCompleted. The reason it was wrong is that it did not support awaiting.

As a rule, any Async code you write WILL need synchronizing at some point, so must be awaitable. This is the reason why async void is a very very dodgy thing and should only be used for event handlers, but lets not even get into that.

Now when I was talking to the chap who wrote the original stuff, I pointed out a more typical use case would have been to do something like this:

C#
class Program
{
    static void Main(string[] args)
    {
        new Program().Backup();
        Console.ReadLine();
    }

    public async void Backup()
    {
        var backupStatus = await BackupAsync();
    }

    public async Task<string> BackupAsync()
    {
        return await Task.Run(() => "groovy");
    }
}

Another eagle eyed reader pointed out that you should never use async void (I was doing that to be as much in line with the original poster's code), and that the only reason my code worked was due to the use of the Console.ReadLine() which was blocking the main thread.

I did know that I should never really use async void, so I set about trying to post a better version of this. One important note here is that I am using a ConsoleApplication. So I tried this:

C#
class Program
{
    private  static async void Main(string[] args)
    {
        await new Program().Backup();
    }

    public async Task Backup()
    {
        var backupStatus = await BackupAsync();
        await Task.Delay(5000); //simulate some work
        Console.WriteLine(backupStatus);
    }

    public async Task<string> BackupAsync()
    {
        return await Task.Run(() => "groovy");
    }
}

The compiler complained about this, and would not allow an async void main method. This is the error.

‘NonAwaitingConsoleApplication.Program.Main(string[])’: an entry point cannot be marked with the 
‘async’ modifier

Ok, so how about this one then:

C#
class Program
{
    private static void Main(string[] args)
    {
        Task.Run(() => MainAsync(args)).Wait();
    }

    static async void MainAsync(string[] args)
    {
        Console.WriteLine(DateTime.Now.ToLongTimeString());
        await new Program().Backup();
        Console.WriteLine(DateTime.Now.ToLongTimeString());
    }

    public async Task Backup()
    {
        var backupStatus = await BackupAsync();
        await Task.Delay(5000); //simulate some work
        Console.WriteLine(backupStatus);
    }

    public async Task<string> BackupAsync()
    {
        return await Task.Run(() => "groovy");
    }
}

And that exited straight away…mmmmm. Interesting. The reason for this is that there is no SynchronizationContext in a ConsoleApplication. So the thread is simply returned to the OS, and the app exits prematurely. Not quite what we are after. What can we do?

Well, luckily there is a very clever chap (who I highly rate) called Stephen Cleary, who has written a nice set of Extensions which is also written by Stephen Toub (he knows his onions for sure), so I have full trust in this library. It is called NitoAsyncEx. It is also available via NuGet: Nito.AsyncEx

Anyway, with this in place, we can now create an Awaiting ConsoleApplication like this:

C#
internal class Program
{
    private static void Main(string[] args)
    {
        AsyncContext.Run(() => MainAsync(args));
    }

    static async void MainAsync(string[] args)
    {
        Console.WriteLine(DateTime.Now.ToLongTimeString());
        await new Program().Backup();
        Console.WriteLine(DateTime.Now.ToLongTimeString());
    }

    public async Task Backup()
    {
        var backupStatus = await BackupAsync();
        await Task.Delay(5000); //simulate some work
        Console.WriteLine(backupStatus);
    }

    public async Task<string> BackupAsync()
    {
        return await Task.Run(() => "groovy");
    }
}

Notice the use of the AsyncContext that is the NitoAsyncEx magic class that makes it all possible. I urge you to have a look inside that class (you can do so using Reflector or via the Codeplex site source tab), and see how it works. Stephen is doing quite a bit on your behalf, like ensuring there is a valid SynchronizationContext. Please have a look at it. In fact, look at it all it's a very useful library

Which when run produces the following output, as expected, then exits (as expected).

  • 10:52:21
  • groovy
  • 10:52:26

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)