How to chain methods in .net with async/await

All the code presented I uploaded as a LinqPad query, so You can try it right away.

Functional programming has a concept of monad (unfamiliar C# programmers I strongly recommend to start with the link provided). A C# task may be considered a monad, and as far as I understand it is exactly what You need.

For the purpose of this answer I made a simplified example of what You have:

await (await (await A.GetNumber()).DoubleIt()).SquareIt()

where the methods are as follows (defined as static just for my convenience):

public static class A
{
    public static Task<int> GetNumber(){return Task.FromResult(3);}
    public static Task<int> DoubleIt(this int input){return Task.FromResult(2 * input);}
    public static Task<int> SquareIt(this int input){return Task.FromResult(input * input);}
}

Now You can easily chain them with just a bit of glue which can look like this:

public static async Task<TOut> AndThen<TIn, TOut>(this Task<TIn> inputTask, Func<TIn, Task<TOut>> mapping)
{
    var input = await inputTask;
    return (await mapping(input));
}

The AndThen method acts exactly like a monadic bind:

await A
     .GetNumber()
     .AndThen(A.DoubleIt)
     .AndThen(A.SquareIt)

What's more important, C# has nice syntax for working with monads: the LINQ query comprehension syntax. You just need to define a SelectMany method which works with the type You desire (Task in this case) and You're ready to go.

Below I implemented the most "hardcore" overload of SelectMany (with additional resultSelector) which gives You the most flexibility. The simple version would be almost exactly the same as AndThen (I think just renaming would do the job).

public static async Task<TOut> SelectMany<TIn, TInterm, TOut>(
   this Task<TIn> inputTask,
   Func<TIn, Task<TInterm>> mapping,
   Func<TIn, TInterm, TOut> resultSelector)
{
    var input = await inputTask;
    return resultSelector(input, await mapping(input));
}

With it You can use the syntax:

var task = 
    from num in A.GetNumber()
    from doubled in num.DoubleIt()
    from squared in num.SquareIt()
    select $"number: {num} doubled: {doubled}, squared: {squared}";
    
Console.WriteLine(await task);

And You get number: 3 doubled: 6, squared: 9.

The simple SelectMany version would allow You to use squared as the only possible expression in the final select line. The "hardcore" version lets You use any expression that uses any of the values defined after a from keyword.