Click here to Skip to main content
12,508,112 members (54,268 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

10.9K views
2 bookmarked
Posted

Awaitable Console Application

, 1 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

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:

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.

&lsquo;NonAwaitingConsoleApplication.Program.Main(string[])&rsquo;: an entry point cannot be marked with the 
&lsquo;async&rsquo; modifier

Ok, so how about this one then:

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:

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)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

You may also be interested in...

Pro
Pro

Comments and Discussions

 
GeneralMy vote of 5 Pin
DontSailBackwards25-Jan-16 0:29
memberDontSailBackwards25-Jan-16 0:29 
SuggestionEven shorter and without 3rd party libraries... Pin
Leonneke2-May-14 3:29
memberLeonneke2-May-14 3:29 
GeneralRe: Even shorter and without 3rd party libraries... Pin
Sacha Barber2-May-14 4:46
mvpSacha Barber2-May-14 4:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.160927.1 | Last Updated 1 May 2014
Article Copyright 2014 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid