6

Inlining functions is a compiler optimization that replaces the function call site with the body of the callee. This optimization is supported for regular C# functions.

Async functions that support the async-await pattern in C# 5.0 have a special declaration that involves the async modifier and wrapping return values with Task<>.

Can async functions be inlined as well?

Example:

Suppose I have these functions:

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

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

Could they be optimized to:

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

Extra Credit:

If it is supported, who can decide what's inlined? The compiler? The JIT? me?

If it's not supported, should I worry about this and avoid excessive wrappers I sometimes add for readability?

Community
  • 1
  • 1
talkol
  • 12,564
  • 11
  • 54
  • 64
  • No need to create one more task. `private Task CalleeAsync() { return SomeOperationAsync(); }` – I4V Sep 02 '13 at 07:38
  • @I4V Of course. Keep in mind this is just a simplistic conceptual example. Real life scenarios will probably be slightly more complicated :) – talkol Sep 02 '13 at 07:40

2 Answers2

4

Considering the complexity of the transformations caused by async/await, I don't think the code is inlineable: async/await cause your method to be tranformed in an hidden class and your code becomes a state machine, with various parts of the code becoming different states.

To give an example, a simple method as CalleeAsync() is transformed to a monster like:

[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;
    }
}

(note that on this machine I still have the Visual Studio 2010 with the Async CTP, with .NET 4.5 the generated code could be different).

Do you think something like that is inlineable?

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • 2
    Well, if I had to go with what I thought I guess I'd say most compiler optimizations were impossible :) What if the compiler tried to inline before compiling into this state-machine monster? I can see this happening on a pre-processor level (if one existed), although I don't really know what I'm talking about – talkol Sep 02 '13 at 10:48
  • @talkol The C# compiler doesn't do things particularly fancy, and rarely modifies too much code. I don't think it does inlining (I think it's the JIT that inlines the code) – xanatos Sep 02 '13 at 11:11
1

The C# compiler will create quite much IL code for a "simple" await, xanatos already showed how that IL code would look like when translated back to C#. Since the JIT compiler (that's the one who does inlining) has strict rules about inlining, I doubt that it will inline all that code.

Here are some rules for JIT inlining (those might not be true for the current JIT, but are a good start). Here I can already see two rules violated: We have more than 32 byte of IL code and an exception handling block.

cremor
  • 6,669
  • 1
  • 29
  • 72