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

Я экспериментировал с Visual Studio 14 CTP 2. Эта версия C# 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 до C# 6.0.

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

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

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


person Yuval Itzchakov    schedule 21.07.2014    source источник


Ответы (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) и убедиться, что результат будет ожидаемым в каждом случае.

Что касается того, почему этого не было в C# 5, Мэдс Торгерсен пишет в CTP-документе C# 6:

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

person Jon Skeet    schedule 21.07.2014
comment
Как я уже сказал в своем посте, это очень похоже на то, как мы это делаем до C# 6.0. При этом я пытаюсь увидеть, есть ли что-то особенное, то есть была ли достаточно веская причина, чтобы НЕ допустить, чтобы это произошло, когда функция async-await была выпущена. - person Yuval Itzchakov; 21.07.2014
comment
@YuvalItzchakov: Я полагаю, что команда C # в тот момент не была убеждена, что это необходимо или обязательно безопасно. Требуется много работы, чтобы проверить правильность такого преобразования. - person Jon Skeet; 21.07.2014
comment
Может быть, мне следует искать те крайние случаи, когда это нетривиально реализовать, чтобы понять сложность. - person Yuval Itzchakov; 21.07.2014
comment
@YuvalItzchakov: ThreadAbortException? Хотя я полагаю, что это легко обойти, вызвав ResetAbort. - person Brian; 21.07.2014
comment
@Brian: Да, это и любые другие асинхронные исключения прошли бы через блок finally... - person Jon Skeet; 21.07.2014
comment
Что произойдет, если при выполнении этого кода будет выгружен AppDomain? Будет ли это выполняться как обычное тело finally? - person Yuval Itzchakov; 21.07.2014
comment
@YuvalItzchakov: Честно говоря, я не знаю. В любом случае, если это возможно, я бы постарался не полагаться на выполнение кода в таких сценариях. - person Jon Skeet; 21.07.2014
comment
Да, конечно. Это просто я представляю крайние случаи такого кода - person Yuval Itzchakov; 21.07.2014
comment
@YuvalItzchakov: Смотрите мое редактирование (нижняя часть моего ответа), чтобы узнать, почему. - person Jon Skeet; 22.07.2014
comment
@JonSkeet Спасибо. Есть ли способ получить доступ к CTP C # 6? - person Yuval Itzchakov; 22.07.2014
comment
@Юваль: абсолютно. Просто зайдите на roslyn.codeplex.com. Но, учитывая ваш вопрос, он у вас уже есть... - person Jon Skeet; 22.07.2014
comment
@JonSkeet Я надеялся на что-то большее в духе раннего проекта спецификации C # 6. - person Yuval Itzchakov; 22.07.2014
comment
@YuvalItzchakov: Не спецификация как таковая, а codeplex.com/Download?ProjectName=roslyn&DownloadId= 881333 и roslyn.codeplex.com/ являются самыми близкими, о которых я знаю. - person Jon Skeet; 22.07.2014