await in try-finally block
Asked Answered
H

1

22

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?

Hypno answered 21/7, 2014 at 14:31 Comment(0)
I
24

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.

Incapacity answered 21/7, 2014 at 14:36 Comment(13)
As i said in my post, this look very similar to the way we do this pre C# 6.0 way. With that, im trying to see if there is anything "special", meaning, was there a good enough reason to NOT allow this to happen when the async-await feature was released.Hypno
@YuvalItzchakov: I believe the C# team basically wasn't convinced at that point that it was required or necessarily safe. There's a lot of work involved in checking that this kind of transformation does the right thing.Incapacity
Maybe i should be looking for those edge cases when this isn't trivial to implement in order to understand the complexity.Hypno
@YuvalItzchakov: ThreadAbortException? Though I suppose it's easy to work around by calling ResetAbort.Cattan
@Brian: Yes, that and any other asynchronous exceptions would "get past" the finally block...Incapacity
What happeneds if while executing this code an AppDomain is being unloaded? Will this execute as a normal finally body would?Hypno
@YuvalItzchakov: I honestly don't know, to be honest. I would try to avoid relying on code being executed in such scenarios anyway, if possible.Incapacity
Yes, of course. This is just me imagining edge cases of such codeHypno
@YuvalItzchakov: See my edit (the bottom of my answer) for an update on the "why" aspect.Incapacity
@JonSkeet Thank you. Is there a way to get ahold of the C# 6 CTP?Hypno
@Yuval: absolutely. Just go to roslyn.codeplex.com. But given your question, you already have it...Incapacity
@JonSkeet I was hoping for something more along the lines of an early draft of the C# 6 specification.Hypno
@YuvalItzchakov: Not a spec as such, but codeplex.com/Download?ProjectName=roslyn&DownloadId=881333 and roslyn.codeplex.com/… are the closest I'm aware of.Incapacity

© 2022 - 2024 — McMap. All rights reserved.