How to use async / await in .NET 4

This article investigates the requirements for programming with the new keywords for the .NET-Framework 4.

The possibility of including the new keywords async and await in the source code is a fantastic opportunity to make existing programs smoother and future apps more dynamic. However, a problem might occur when trying to use those keywords with the .NET 4.0 as target framework.

First of all let's have a look at another problem that might arise: the lack of Visual Studio 2012. Without VS 2012 we are stuck with an outdated C# compiler. The C# compiler of VS 2010 does not support the two keywords, since they have been introduced in C# 5. The IDE, however, just comes with a C# 4 compiler. In this case we require the so called Async CTP. Personally, I found it a mess to install, since already rolled out bugfixes might interfere with the installation. Therefore one has to make sure uninstall some of the updates that might interfere with the Async CTP. More information on this topic can be found in the references below.

From this point on we assume that we have the right compiler (C# 5) and are probably using VS 2012. Now we can open one of our old solutions or create a new one with .NET-Framework 4 as target framework. Trying the following code will ultimately result in errors:

void Main()
{
	MyAsyncTest();	
}

async void MyAsyncTest()
{
	var task = new Task(GiveMeTheResult);
	task.Start();
	"Wurde gestartet".Dump();
	var something = await task;
	something.Dump();
}

string GiveMeTheResult()
{
	Thread.Sleep(2000);
	return "Work Done";
}

Here we will get some weird error message. The question now is: what can possibly go wrong? One educated guess would be, that the wrapper that is included in the compiler for resolving the await statement makes use of an object, that is not available in .NET-Framework 4, since it has been introduced in .NET 4.5.

This is the point where we can use LINQPad to have a close look at the IL output. We will use LINQPad with .NET 4.5 as target framework - so that we see what the output is supposed to look like.

IL_0001:  ldarg.0     
IL_0002:  call        UserQuery.MyAsyncTest

MyAsyncTest:
IL_0000:  ldloca.s    00 
IL_0002:  ldarg.0     
IL_0003:  stfld       UserQuery+d__0.<>4__this
IL_0008:  ldloca.s    00 
IL_000A:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Create
IL_000F:  stfld       UserQuery+d__0.<>t__builder
IL_0014:  ldloca.s    00 
IL_0016:  ldc.i4.m1   
IL_0017:  stfld       UserQuery+d__0.<>1__state
IL_001C:  ldloca.s    00 
IL_001E:  ldfld       UserQuery+d__0.<>t__builder
IL_0023:  stloc.1     
IL_0024:  ldloca.s    01 
IL_0026:  ldloca.s    00 
IL_0028:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Start
IL_002D:  br.s        IL_002F
IL_002F:  ret         

GiveMeTheResult:
IL_0000:  nop         
IL_0001:  ldc.i4      D0 07 00 00 
IL_0006:  call        System.Threading.Thread.Sleep
IL_000B:  nop         
IL_000C:  ldstr       "Work Done"
IL_0011:  stloc.0     
IL_0012:  br.s        IL_0014
IL_0014:  ldloc.0     
IL_0015:  ret         

d__0.MoveNext:
IL_0000:  ldc.i4.1    
IL_0001:  stloc.0     
IL_0002:  ldarg.0     
IL_0003:  ldfld       UserQuery+d__0.<>1__state
IL_0008:  stloc.2     
IL_0009:  ldloc.2     
IL_000A:  ldc.i4.s    FD 
IL_000C:  beq.s       IL_0014
IL_000E:  ldloc.2     
IL_000F:  ldc.i4.0    
IL_0010:  beq.s       IL_0019
IL_0012:  br.s        IL_001B
IL_0014:  br          IL_00CA
IL_0019:  br.s        IL_0087
IL_001B:  br.s        IL_001D
IL_001D:  nop         
IL_001E:  ldarg.0     
IL_001F:  ldarg.0     
IL_0020:  ldfld       UserQuery+d__0.<>4__this
IL_0025:  ldftn       UserQuery.GiveMeTheResult
IL_002B:  newobj      System.Func..ctor
IL_0030:  newobj      System.Threading.Tasks.Task..ctor
IL_0035:  stfld       UserQuery+d__0.5__1
IL_003A:  ldarg.0     
IL_003B:  ldfld       UserQuery+d__0.5__1
IL_0040:  callvirt    System.Threading.Tasks.Task.Start
IL_0045:  nop         
IL_0046:  ldstr       "Wurde gestartet"
IL_004B:  call        LINQPad.Extensions.Dump
IL_0050:  pop         
IL_0051:  ldarg.0     
IL_0052:  ldfld       UserQuery+d__0.5__1
IL_0057:  callvirt    System.Threading.Tasks.Task.GetAwaiter
IL_005C:  stloc.3     
IL_005D:  ldloca.s    03 
IL_005F:  call        System.Runtime.CompilerServices.TaskAwaiter.get_IsCompleted
IL_0064:  brtrue.s    IL_00A5
IL_0066:  ldarg.0     
IL_0067:  ldc.i4.0    
IL_0068:  stfld       UserQuery+d__0.<>1__state
IL_006D:  ldarg.0     
IL_006E:  ldloc.3     
IL_006F:  stfld       UserQuery+d__0.<>u__$awaiter3
IL_0074:  ldarg.0     
IL_0075:  ldflda      UserQuery+d__0.<>t__builder
IL_007A:  ldloca.s    03 
IL_007C:  ldarg.0     
IL_007D:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.AwaitUnsafeOnCompleted
IL_0082:  nop         
IL_0083:  ldc.i4.0    
IL_0084:  stloc.0     
IL_0085:  leave.s     IL_00F9
IL_0087:  ldarg.0     
IL_0088:  ldfld       UserQuery+d__0.<>u__$awaiter3
IL_008D:  stloc.3     
IL_008E:  ldarg.0     
IL_008F:  ldloca.s    04 
IL_0091:  initobj     System.Runtime.CompilerServices.TaskAwaiter
IL_0097:  ldloc.s     04 
IL_0099:  stfld       UserQuery+d__0.<>u__$awaiter3
IL_009E:  ldarg.0     
IL_009F:  ldc.i4.m1   
IL_00A0:  stfld       UserQuery+d__0.<>1__state
IL_00A5:  ldloca.s    03 
IL_00A7:  call        System.Runtime.CompilerServices.TaskAwaiter.GetResult
IL_00AC:  ldloca.s    03 
IL_00AE:  initobj     System.Runtime.CompilerServices.TaskAwaiter
IL_00B4:  stloc.s     05 
IL_00B6:  ldarg.0     
IL_00B7:  ldloc.s     05 
IL_00B9:  stfld       UserQuery+d__0.5__2
IL_00BE:  ldarg.0     
IL_00BF:  ldfld       UserQuery+d__0.5__2
IL_00C4:  call        LINQPad.Extensions.Dump
IL_00C9:  pop         
IL_00CA:  leave.s     IL_00E4
IL_00CC:  stloc.1     
IL_00CD:  ldarg.0     
IL_00CE:  ldc.i4.s    FE 
IL_00D0:  stfld       UserQuery+d__0.<>1__state
IL_00D5:  ldarg.0     
IL_00D6:  ldflda      UserQuery+d__0.<>t__builder
IL_00DB:  ldloc.1     
IL_00DC:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.SetException
IL_00E1:  nop         
IL_00E2:  leave.s     IL_00F9
IL_00E4:  nop         
IL_00E5:  ldarg.0     
IL_00E6:  ldc.i4.s    FE 
IL_00E8:  stfld       UserQuery+d__0.<>1__state
IL_00ED:  ldarg.0     
IL_00EE:  ldflda      UserQuery+d__0.<>t__builder
IL_00F3:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.SetResult
IL_00F8:  nop         
IL_00F9:  nop         
IL_00FA:  ret         

d__0.SetStateMachine:
IL_0000:  ldarg.0     
IL_0001:  ldflda      UserQuery+d__0.<>t__builder
IL_0006:  ldarg.1     
IL_0007:  call        System.Runtime.CompilerServices.AsyncVoidMethodBuilder.SetStateMachine
IL_000C:  ret         

I left all the output here, since it might give some interesting information on how the whole thing is built from the compiler. The basic information one should get about this code are the following two statements:

System.Runtime.CompilerServices.AsyncVoidMethodBuilder

This one is used when we declare a method to be async.

System.Runtime.CompilerServices.TaskAwaiter

This one is obviously used once we have an await in the code.

Aha! So here we have something that looks interesting and mysterious. If we look at the MSDN entry of the TaskAwaiter structure, we find out that is has been released together with the .NET-Framework 4.5. Therefore we can now be sure that this is the one that's been missing...

So the two new keywords just make sure that the compiler knows how to package everything with the help of the new (hidden) objects. Of course the await is where most of the magic is happening; so this is the real crucial part here.

Long story short: Microsoft does already know about this problem (of course they do - they put something into the compiler directives that makes use of something they just invented and put into .NET-Framework 4.5!). They put a package on NuGet, which should be installed if one thinks about using async / await with previous versions of the .NET-Framework. Installation can be done with the following powershell statement:

install-package Microsoft.Bcl.Async –pre

This is a pre-release of this package. Another way is to use an official and already released one like: Microsoft.CompilerServices.AsyncTargetingPack. More information on this one can be found on the NuGet page. Installation is possible by using the following command:

Install-Package Microsoft.CompilerServices.AsyncTargetingPack

This option requires Visual Studio 2012 to be installed - it is not possible with the Async CTP for Visual Studio 2010!

Created .

References

Sharing is caring!