【问题标题】:await in try-finally block在 try-finally 块中等待
【发布时间】:2014-09-12 02:39:51
【问题描述】:

我一直在使用 Visual Studio 14 CTP 2。这个版本的 C# vNext 允许在 finally 块中使用 await 关键字。

我试图弄清楚这是如何实现的。我知道这是一个实现细节,并且在 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 在 C# 6.0 之前,我们必须做一些类似的事情。

我看到了一些我不明白的事情:

  1. 编译器正在添加一个生成的 catch 块(不是 以catch (object obj2) 的形式出现在我的方法中)和 将其内部 object 设置为 obj2 异常。我不明白为什么要这样做。

  2. 我创建的 finally 块不再存在。这是否意味着 finally 块内的任何 awaited 代码都不会 获得“享受”我们从实际放置代码中获得的保证 在这样一个街区内?

【问题讨论】:

    标签: c# compiler-construction async-await try-finally c#-6.0


    【解决方案1】:

    编译器正在转动:

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

    变成类似:

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

    ...但是以异步方式。它最终得到相同的结果 - 无论是否引发异常,您的 finally 块仍将执行,它只是使用“捕获所有内容然后执行”而不是 finally 的 IL 版本。

    我建议您考虑在各种情况下执行流程会是什么样子(例如,在 try 块中是否抛出异常)并说服自己在每种情况下结果都是预期的。

    关于为什么这不在 C# 5 中,Mads Torgersen 在 C# 6 CTP 文档中写道:

    在 C# 5.0 中,我们不允许在 catchfinally 块中使用 await 关键字,因为我们以某种方式说服自己无法实现。现在我们已经弄清楚了,显然这毕竟不是不可能的。

    【讨论】:

    • 正如我在帖子中所说,这看起来与我们在 C# 6.0 之前的方式非常相似。有了这个,我想看看是否有什么“特殊”,意思是,当async-await 功能发布时,是否有足够的理由允许这种情况发生。
    • @YuvalItzchakov:我相信 C# 团队当时基本上不相信它是必需的或必然安全的。要检查这种转换是否正确,需要做很多工作。
    • 也许我应该寻找那些边缘情况,以便了解复杂性。
    • @YuvalItzchakov: ThreadAbortException?虽然我认为调用ResetAbort 很容易解决。
    • @Brian:是的,任何其他异步异常都会“通过”finally 块...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-20
    • 2013-12-16
    • 1970-01-01
    • 2011-08-31
    • 2015-09-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多