Deep dive to async Main

C# 7.1 introduces asynchronous Main method for console applications. It helps us to get rid of some ugly code to await for asynchronous methods. If Main method of console applications is application flow controller that makes multiple calls to asynchronous methods then async Main leads us to much cleaner code. This blog post is my deep dive to async Main.

Suppose we have console application built on .NET Core 2.0. Our application performs some simple tasks using asynchronous calls. We end up with Main method like shown below (I use dummy code to keep it simple).


class Program
{
    public static void Main(string[] args)
    {  
      Task.Delay(0).GetAwaiter().GetResult();
        Console.WriteLine("Press Enter to continue ...");
        Console.ReadLine();
    }
}

It works okay and technically there is nothing wrong but still we drag out the fact that we have to wait for async method to finish. It’s not a big deal but when we look at it from platform provider side then there will be tons of solutions that repeat the same thing over and over again: wait until asynchronous method called in Main finishes.

Async Main

From C# 7.1 there is support for asynchronous Main method at framework level meaning that there is no more need to manually wait for async methods to complete and it’s very simple. The previous code can be written like here.


class Program
{
    public static async Task Main(string[] args)
    {     
    await Task.Delay(0);
        Console.WriteLine("Press Enter to continue ...");
        Console.ReadLine();
    }
}

But what about console applications returning integer? These are supported too. Here is the full list of supported async Main methods:

  • public static Task Main();
  • public static Task<int> Main();
  • public static Task Main(string[] args);
  • public static Task<int> Main(string[] args);

I think these overloads should be enough for console applications.

Who awaits?

As Main method is entry point on application we can ask how await is implemented? Main is topmost method of application.

To answer the question let’s see what’s going on behind the compiler. After building solution (in case of errors skip for a moment to next section) let’s see what’s inside DLL-file using dotPeek by JetBrains.


namespace AsyncMain
{
    internal class Program
    {
        public static async Task Main(string[] args)
        {
            await Task.Delay(0);
            Console.WriteLine("Press Enter to continue ...");
            Console.ReadLine();
        }

        [SpecialName]
        private static void <Main>(string[] args)
        {
            Program.Main(args).GetAwaiter().GetResult();
        }
    }
}

The trick here is private version of Main method that is invisible to IDE-s and code editors. This is the actual entry point of application and we can prove it by checking out the IL code for <Main> method.

Async Main stub in ILDASM

SpecialNameAttribute is added to all special methods by compiler. Don’t jump to any conclusions as this attribute is actually not used right now. Documentation sais it is reserved for future use.

Emulating async Main on earlier versions of C#

As we saw then running asynchronous code in synchronous method involves some simple tricks that are easy to produce. Still we would like to hide these tricks away or at least move to somewhere else so we doesn’t pollute our own code. On C# 7.1 things were simple but what we can do with applications built on earlier versions?

Simple thing to do is to have classic Main method that calls for asynchronous one that is part of our application. The point is to keep classic Main method as a stub that implements awaiting logic.


class Program
{
    public static void Main(string[] args)
    {
        AsyncMain(args).GetAwaiter().GetResult();
        Console.WriteLine("Press Enter to continue ...");
        Console.ReadLine();
    }
    private static async Task AsyncMain(string[] args)
    {
         await Task.Delay(0);
    }
}

It’s similar solution that C# 7.1 hides behind the compiler. As we cannot hide anything in Visual Studio IDE we can just agree that let’s handle classic Main method as a minimal stub and implement all actual Main method logic in AsyncMain method.

Activating C# 7.1 features

If console application with async Main doesn’t compile it’s possible that Visual Studio is not using C# 7.1 compiler. To switch over to newer compiler open project properties and advanced build settings. Select C# 7.1 as language version.

advanced-language-settings

Click Okay and build solution again.

Wrapping up

Async main doesn’t seem like a big thing and it only cleans some ugliness out from Main method of console applications. Still we get cleaner code and at least for me this is important thing to achieve. Additionally it also decreases danger that some more artistic developers start inventing their own hacks to wait for async method to return. One may ask how much cleaner the code gets dut to this small language feature? Well, like always, it depends, but the rule of thumb is simple: the more async method calls in Main the bigger the effect of async Main. Async main is called by hidden <Main> method that is like the classic one. It is automatically created by compiler and it is not visible for development tools. Still it is easy to emulate it on earlier versions of C# to keep code cleaner.



See also

One thought on “Deep dive to async Main

Leave a Reply

Your email address will not be published. Required fields are marked *