【问题标题】:Can async functions be inlined?可以内联异步函数吗?
【发布时间】:2013-09-02 07:28:56
【问题描述】:

Inlining functions 是一种编译器优化,它将函数调用站点替换为被调用者的主体。 regular C# functions 支持此优化。

在 C# 5.0 中支持async-await 模式的Async 函数有一个特殊声明,其中涉及async 修饰符并用Task<> 包装返回值。

异步函数也可以内联吗?

示例:

假设我有这些功能:

private async Task<int> CalleeAsync() {
    return await SomeOperationAsync();
}

private async Task<int> CallerAsync() {
    return await CalleeAsync();
}

它们是否可以优化为:

private async Task<int> CallerAsync() {
    return await SomeOperationAsync();
}

额外积分:

如果支持,谁可以决定内联的内容?编译器? JIT?我?

如果它不受支持,我是否应该担心这一点并避免为了可读性而有时添加的过多包装器?

【问题讨论】:

  • 无需再创建一项任务。 private Task&lt;int&gt; CalleeAsync() { return SomeOperationAsync(); }
  • @I4V 当然。请记住,这只是一个简单的概念示例。现实生活中的场景可能会稍微复杂一些:)

标签: c# .net optimization asynchronous async-await


【解决方案1】:

考虑到async/await 引起的转换的复杂性,我不认为代码是可内联的:async/await 导致你的方法在隐藏类中被转换,你的代码变成了一个状态机,各个部分代码变成不同的状态。

举个例子,一个简单的方法CalleeAsync()被转换成一个怪物像:

[CompilerGenerated]
private sealed class <CalleeAsync>d__2
{
    private int <>1__state;
    private bool $__disposing;
    public System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> $builder;
    public Action <>t__MoveNextDelegate;
    public Program <>4__this;
    private TaskAwaiter<int> <a1>t__$await4;
    public void MoveNext()
    {
        int result2;
        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> asyncTaskMethodBuilder;
        try
        {
            int num = this.<>1__state;
            if (num != 1)
            {
                if (this.<>1__state == -1)
                {
                    return;
                }
                this.<a1>t__$await4 = this.<>4__this.SomeOperationAsync().GetAwaiter<int>();
                if (!this.<a1>t__$await4.IsCompleted)
                {
                    this.<>1__state = 1;
                    this.<a1>t__$await4.OnCompleted(this.<>t__MoveNextDelegate);
                    return;
                }
            }
            else
            {
                this.<>1__state = 0;
            }
            int result = this.<a1>t__$await4.GetResult();
            this.<a1>t__$await4 = default(TaskAwaiter<int>);
            result2 = result;
        }
        catch (Exception exception)
        {
            this.<>1__state = -1;
            asyncTaskMethodBuilder = this.$builder;
            asyncTaskMethodBuilder.SetException(exception);
            return;
        }
        this.<>1__state = -1;
        asyncTaskMethodBuilder = this.$builder;
        asyncTaskMethodBuilder.SetResult(result2);
    }
    [DebuggerHidden]
    public void Dispose()
    {
        this.$__disposing = true;
        this.MoveNext();
        this.<>1__state = -1;
    }
    [DebuggerHidden]
    public <CalleeAsync>d__2(int <>1__state)
    {
        this.<>1__state = <>1__state;
    }
}

(请注意,在这台机器上我仍然有带有异步 CTP 的 Visual Studio 2010,使用 .NET 4.5 生成的代码可能会有所不同)。

你认为这样的东西是可内联的吗?

【讨论】:

  • 好吧,如果我不得不按照我的想法去做,我会说大多数编译器优化是不可能的:) 如果编译器在编译成这个状态机怪物之前尝试内联怎么办?我可以看到这发生在预处理器级别(如果存在的话),虽然我真的不知道我在说什么
  • @talkol C# 编译器不会做特别花哨的事情,并且很少修改太多代码。我认为它不会内联(我认为是内联代码的 JIT)
【解决方案2】:

C# 编译器将为“简单”await 创建相当多的 IL 代码,xanatos 已经展示了这些 IL 代码在翻译回 C# 时的样子。由于 JIT 编译器(即进行内联的编译器)对内联有严格的规定,我怀疑它是否会内联所有代码。

Here 是 JIT 内联的一些规则(这些规则可能不适用于当前的 JIT,但这是一个好的开始)。在这里我已经可以看到违反了两条规则:我们有超过 32 字节的 IL 代码和一个异常处理块。

【讨论】:

    猜你喜欢
    • 2011-12-30
    • 2010-09-16
    • 2019-08-18
    • 2011-08-24
    • 1970-01-01
    相关资源
    最近更新 更多