tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

How does asynchronous Main work under the hood?

31 Oct 2017 .NET, .NET Core, C#, Roslyn

C# 7.1 comes with option to use asynchronous Main method. That means now the Main can have async modifier and return Task or Task<int>. But how it works under the hood? Let’s find out.

Given this feature is available from C# 7.1 it’s clearly just compiler feature not relying on anything from say CLR. Thus the compiler is doing “just” some transformation.

History

Before C# 7.1 if you wanted to have asynchronous Main, you has basically two options.

The straightforward using Wait.

static void Main(string[] args)
{
    MainAsync().Wait();
}

Or, if you knew little bit of inner workings, using GetAwaiter and GetResult.

static void Main(string[] args)
{
    MainAsync().GetAwaiter().GetResult();
}

These two approaches are not exactly the same, in particular concerning exceptions, but both get the job done. Is compiler doing something else? Something smarter?

C# 7.1 version

I created an empty console application, with empty static async Task Main(string[] args) and added <LangVersion>7.1</LangVersion> to the csproj. Opened good old ildasm and here’s the result.

.method private hidebysig specialname static 
        void  '<Main>'(string[] args) cil managed
{
  .entrypoint
  // Code size       20 (0x14)
  .maxstack  1
  .locals init (valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter V_0)
  IL_0000:  ldarg.0
  IL_0001:  call       class [System.Runtime]System.Threading.Tasks.Task ConsoleApp1.Program::Main(string[])
  IL_0006:  callvirt   instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter [System.Runtime]System.Threading.Tasks.Task::GetAwaiter()
  IL_000b:  stloc.0
  IL_000c:  ldloca.s   V_0
  IL_000e:  call       instance void [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter::GetResult()
  IL_0013:  ret
} // end of method Program::'<Main>'

It generated new <Main>(string[] args) method and the body is simple Program.Main(args).GetAwaiter().GetResult();. If the Main returns int, then the method is basically the same, only having explicit return of value from GetResult.

Conclusion

There you have it. Nothing special and absolutely straightforward. So even if you can’t use C# 7.1, you can write this yourself without any real effort.

Profile Picture Jiří Činčura is an independent developer focusing on data and business layers, language constructs, parallelism and databases. Specifically Entity Framework, asynchronous and parallel programming, cloud and Azure. He's Microsoft Most Valuable Professional and you can read his articles, guides, tips and tricks at www.tabsoverspaces.com.