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.
- Default literal expressions in C# 7.1
- Inferred tuple names in C# 7.1
- 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.
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 version open project properties and advanced build settings. Select C# 7.1 as language version.
Click Okay and build solution again.
Practical use case. As many libraries today are providing async calls by default the use of async Main will also gain more popularity. One good example is Entity Framework Core 2.0 that introduced async counterparts for most important querying methods. Read my blog post Entity Framework Core with .NET Core console application for detailed example of async Main and Entity Framework Core.
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.
More compiler secrets
- Are lock and Monitor the same in C#?
- Deep dive to async Main
- Extension methods – how they look like after compiling
- C# and var keyword
- C# and anonymous types
- C# and question marks
- IL perversions: throwing and catching strings
Pingback:The Morning Brew - Chris Alcock » The Morning Brew #2480
Hi, I have a question, .net core turn all i/o calls to async/await model but why I would have to use or what would be the benefit of using async/await in a win service or console that run without a UI ?
It really depends on what you service or console application is doing. Using async calls doesn’t make program run faster but what gets better is throughput as machine can serve more threads with same hardware resources.