I've been playing around with the Visual Studio 14 CTP 2. This version of C# vNext enables the use of the await
keyword inside a finally block.
I am trying to figure out how this was implemented. I know this is an implementation detail and is subject to change until the RTM release, but i still had to pick my brain with this feature.
To try and understand the underlying compiler generated code, i created this sample code:
private async void button1_Click(object sender, EventArgs e)
{
try
{
}
finally
{
await MyFinallyTest();
}
}
private async Task MyFinallyTest()
{
await Task.Delay(1000);
}
This is the compiler generated class:
[CompilerGenerated]
private sealed class <button1_Click>d__1 : IAsyncStateMachine
{
public int <>1__state;
public Form1 <>4__this;
public object <>7__wrap1;
public int <>7__wrap2;
public AsyncVoidMethodBuilder <>t__builder;
public TaskAwaiter <>u__$awaiter0;
private void MoveNext()
{
int num = this.<>1__state;
try
{
TaskAwaiter awaiter;
switch (num)
{
case 1:
break;
default:
{
this.<>7__wrap1 = null;
this.<>7__wrap2 = 0;
try
{
}
catch (object obj2)
{
this.<>7__wrap1 = obj2;
}
awaiter = this.<>4__this.MyFinallyTest().GetAwaiter();
if (awaiter.IsCompleted)
{
goto Label_0096;
}
this.<>1__state = num = 1;
this.<>u__$awaiter0 = awaiter;
Form1.<button1_Click>d__1 stateMachine = this;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Form1.<button1_Click>d__1>(ref awaiter, ref stateMachine);
return;
}
}
awaiter = this.<>u__$awaiter0;
this.<>u__$awaiter0 = new TaskAwaiter();
this.<>1__state = num = -1;
Label_0096:
awaiter.GetResult();
awaiter = new TaskAwaiter();
object obj3 = this.<>7__wrap1;
if (obj3 != null)
{
Exception source = obj3 as Exception;
if (source <= null)
{
throw obj3;
}
ExceptionDispatchInfo.Capture(source).Throw();
}
int num1 = this.<>7__wrap2;
this.<>7__wrap1 = null;
}
catch (Exception exception2)
{
this.<>1__state = -2;
this.<>t__builder.SetException(exception2);
return;
}
this.<>1__state = -2;
this.<>t__builder.SetResult();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}
From what i understand, the compiler is taking the finally
code block and moving it to after the compiler generated catch
block. Something similar to what we had to do if we wanted to await
something in a catch-finally up untill C# 6.0.
I see a couple of things i dont understand:
The compiler is adding a generated catch
block (which wasn't
present in my method) in the form of catch (object obj2)
and
setting its internal object
to the obj2 exception. I dont understand why this is being done.
The finally
block which i created no longer exist. Does that mean
that any code which is awaited
inside a finally
block doesn't
get to "enjoy" the guarantees we get from actually putting code
inside such a block?
The compiler is just turning:
try
{
Foo();
}
finally
{
Bar();
}
into something like:
Exception caught = null;
try
{
Foo();
}
catch (Exception e)
{
caught = e;
}
Bar();
if (caught != null)
{
throw caught;
}
... but in an asynchronous way. It ends up with the same result - your finally
block will still execute whether or not an exception is thrown, it's just using "catch everything and then execute" rather than the IL version of finally
.
I suggest you consider what the execution flow would look like in various situations (e.g. whether or not an exception is thrown in the try block) and convince yourself that the result will be the expected one in each case.
In terms of why this wasn't in C# 5, Mads Torgersen writes in a C# 6 CTP document:
In C# 5.0 we don’t allow the
await
keyword incatch
andfinally
blocks, because we'd somehow convinced ourselves that it wasn’t possible to implement. Now we've figured it out, so apparently it wasn't impossible after all.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With