tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

How does asynchronous Main work under the hood?

31 Oct 2017 2 mins .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.

Related post.

Profile Picture Jiří Činčura is .NET, C# and Firebird expert. He focuses on data and business layers, language constructs, parallelism, databases and performance. For almost two decades he contributes to open-source, i.e. FirebirdClient. He works as a senior software engineer for Microsoft. Frequent speaker and blogger at www.tabsoverspaces.com.