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!