Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

await in try-finally block

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:

  1. 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.

  2. 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?

like image 991
Yuval Itzchakov Avatar asked Jul 21 '14 14:07

Yuval Itzchakov


1 Answers

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 in catch and finally 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.

like image 104
Jon Skeet Avatar answered Oct 31 '22 22:10

Jon Skeet