Подтвердить что ты не робот

Ждать в блоке try-finally

Я играл с Visual Studio 14 CTP 2. Эта версия С# vNext позволяет использовать ключевое слово await внутри блока finally.

Я пытаюсь понять, как это было реализовано. Я знаю, что это детализация и может быть изменена до релиза RTM, но мне все же приходилось выбирать мой мозг этой функцией.

Чтобы попытаться понять базовый код, сгенерированный компилятором, я создал этот пример кода:

private async void button1_Click(object sender, EventArgs e)
{
    try
    {
    }
    finally
    {
        await MyFinallyTest();
    }
}

private async Task MyFinallyTest()
{
    await Task.Delay(1000);
}

Это класс, сгенерированный компилятором:

[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)
    {
    }
}

Из того, что я понимаю, компилятор принимает блок кода finally и переносит его после компилятора, сгенерированного блоком catch. Что-то похожее на то, что мы должны были сделать, если бы мы хотели await что-то в catch-finally до С# 6.0.

Я вижу пару вещей, которые я не понимаю:

  • Компилятор добавляет сгенерированный блок catch (который не был присутствуют в моем методе) в виде catch (object obj2) и установив внутренний object в исключение obj2. Я не понимаю, почему это делается.

  • Блок finally, который я создал, больше не существует. Означает ли это что любой код, который является awaited внутри блока finally, не получить "наслаждаться" гарантиями, которые мы получаем от фактического ввода кода внутри такого блока?

4b9b3361

Ответ 1

Компилятор просто поворачивается:

try
{
    Foo();
}
finally
{
    Bar();
}

во что-то вроде:

Exception caught = null;
try
{
    Foo();
}
catch (Exception e)
{
    caught = e;
}
Bar();
if (caught != null)
{
    throw caught;
}

... но с асинхронным способом. Он заканчивается тем же результатом - ваш блок finally все равно будет выполняться независимо от того, генерируется ли исключение, просто используя "уловить все, а затем выполнить", а не версию IL finally.

Я предлагаю вам подумать о том, как будет выглядеть поток выполнения в различных ситуациях (например, выбрано ли исключение в блоке try) и убедите себя, что результат будет ожидаемым в каждом случае.

С точки зрения того, почему это не было на С# 5, Mads Torgersen пишет в CTP-документе С# 6:

В С# 5.0 мы не разрешаем ключевое слово await в блоках catch и finally, потому что мы как-то убедились, что его невозможно реализовать. Теперь мы это выяснили, так что, видимо, это было невозможно в конце концов.