Q: C# Performance Impact of Async function calling async function I was writing code where I ended up calling async method from another async method. I was wondering about performance impact of this practice. Does it leads to using so many threads for each async-await ?
An example would help
public async Task<IHttpActionResult> ControllerMethod() 
{
    :
    return await A1();
}

public async Task<R1> A1() 
{
    :
    var result = await A2();
    if (result != null) 
    {  
        A3() 
    }
    return result;
}

public async Task<R1> A2() 
{
    :
    var result = await A4();
    return result;
}

public void A3() 
{
    :
    // call to another async method without await
}

Please help me understand - is this bad practice ? This was result of refactoring code

A: The cost and performance impact lays into the fact that each method that gets marked as async gets transferred into a statemachine behind the scenes; so more code and statemachine instances that must be taken care of.
You can avoid a statemachine with a rewrite as here below; you don't have to await if there is no code following that statement, just return the task as done in the A2 method below.
public Task<IHttpActionResult> ControllerMethod() 
{
    return A1();
}

public async Task<R1> A1() 
{
    var result = await A2();
    if (result !=  null)
    {  
        A3() 
    }
    return result;
}

public Task<R1> A2() 
{
    return A4();
}

public void A3() 
{
    // call to another async method without await
}


A: This is not at all a bad practice. This is the opposite if the small methods that you're breaking down into does some work that really needs async operations. Suppose in the method A4() you're reading data from a file(A non-CPU binding operation that doesn't need any thread from your ASP .NET thread-pool). If you don't use async in this operation, you're thread will be stuck with the operation doing nothing while the data is being read from the file.
On the contrary, if you use async, the thread will return to the thread-pool, and the thread-pool will be able to serve more request with that newly freed thread.

A: When in doubt you can always test the results. In this benchmark all methods are executing the same code. The only difference is that in some I execute everything on one method and on others I split it into multiple methods.
[MemoryDiagnoser]
public class Test
{
    [Benchmark]
    public async Task TestWithoutCallingOtherMethod()
    {
        // call an async method to make this method async
        await Task.Delay(0);

        var guid = Guid.NewGuid().ToByteArray();

        // function to be extracted ----------------------------------
        int sum = 0;
        // call an async method to make this method async
        await Task.Delay(0);
        foreach (var bt in guid)
            sum += bt;
        // -----------------------------------------------------------

        // will never happen. Include so compiler does not complain
        if (sum == 100000)
            throw new Exception();
    }

    [Benchmark]
    public async Task TestCallingAnotherMethod()
    {
        // call an async method to make this method async
        await Task.Delay(0);

        var guid = Guid.NewGuid().ToByteArray();

        if (await sumBytes(guid) == 1000000)
            throw new Exception();
    }

    async Task<int> sumBytes(byte[] guid)
    {
        // call an async method to make this method async
        await Task.Delay(0);

        int sum = 0;
        foreach (var bt in guid)
            sum += bt;

        return sum;
    }


    [Benchmark]
    public async Task TestCallingAnotherMethodThatIsStatic()
    {
        // call an async method to make this method async
        await Task.Delay(0);

        var guid = Guid.NewGuid().ToByteArray();

        if (await sumBytesStatic(guid) == 1000000)
            throw new Exception();
    }

    static async Task<int> sumBytesStatic(byte[] guid)
    {
        // call an async method to make this method async
        await Task.Delay(0);

        int sum = 0;
        foreach (var bt in guid)
            sum += bt;

        return sum;
    }
}

And this where the results:
|                               Method |     Mean |   Error |  StdDev |  Gen 0 | Allocated |
|------------------------------------- |---------:|--------:|--------:|-------:|----------:|
|        TestWithoutCallingOtherMethod | 343.5 ns | 1.23 ns | 1.02 ns |      - |      40 B |
|             TestCallingAnotherMethod | 345.6 ns | 6.31 ns | 5.27 ns | 0.0010 |     112 B |
| TestCallingAnotherMethodThatIsStatic | 341.9 ns | 3.09 ns | 4.12 ns |      - |     112 B |


This results shows that it is practically the same.
