No doubts that async/await pattern has significantly simplified working with asynchronous operations in C#. However, this simplification relates only to the situation when asynchronous operations are executed consequently. If we need to execute several asynchronous operations simultaneously (e.g. we need to call several micro-services) then we do not have many built-in capabilities and most probably Task.WhenAll will be used: This is a working solution, but it is quite verbose and not very reliable (you can forget to add a new task to “WhenAll”). I would prefer something like that instead:  Further I will tell you what is necessary for this construction to work... First, we should remember that C# query syntax is just a syntax sugar over method call chains and C# preprocessor will convert the previous statement into a cain of SelectMany calls: By default C# Compiler will complain about this code since SelectMany extension method is defined only for IEnumerable interface, but nothing prevents us to create our own overloads of SelectMany to make the code compilable. Each SelectMany function in the chain has two arguments*: (t/*Anon type 1*/) => SomeAsyncOperation3(),/*Task<T2>*/ (t/*Anon type 1*/, r3/*T3*/) => new {t, r3}/*Anon type 2*/) We can call the first functions to get a list of tasks which will be used in Task.WhenAll and then call the second mapping functions to build a result. To get the task list and the list of mapping functions our SelectMany overload needs to return some objects which will contain links to a task and a mapping function. In addition to that, SelectMany receives a links to a previous object as this argument (in case if SelectMany is an extension method) — we also need this link to build a linked list which will
contain all required data.  The initial accumulator differs from the subsequent ones since It cannot have a link to a previous accumulator, but it should have a link to the first task: Now we can get the result by adding these methods: Also, we can add a simple extension method to make await work with ITaskAccumulator: Now the code is working: However, there is an issue here — C# allows using an intermediate result as an argument for further operations. For example: Such code will lead to "Null Reference Exception" since r2 is not yet resolved at the moment when SomeAsyncOperation3 is called:  since all the tasks are run in parallel. Unfortunately, I do not see a solution for that problem in the current state of C# language, but we can mitigate it by dividing tasks in two groups: To do that let's introduce the two simple wrappers over a task: The only purpose of the tasks is to specify what '''SelectMany''' overloads should be used: and (it is a new overload): As you see all previous tasks are resolved trough var arg1/*Anon Type X*/ = await source.Result();, so they can be used to retrieve a next task and the code bellow will work properly: We introduced the task accumulator to get a list of tasks be able to call Task.WhenAll over them. But do we really need it? Actually, we do not!
The thing is that once we received a link to the task it is already started execution and all the task below are running in parallel (the code from the beginning): But instead of Task.WhenAll we can use several await-s: await immediately returns a result if a task is already resolved or waits till an asynchronous operation is completed, so the code will take the same amount of time as if Task.WhenAll was used. That fact allows us to significantly simplify the code and get rid of the task accumulator: That is it. All the code can be found on GitHub ... Developers who familiar with functional programing languages might notice that the approach described above resembles “Monad” design pattern. It is no surprise since C# query notation is a kind of equivalent of “do” notation in Haskell which, in turn, is a “syntax sugar” for working with monads. If you are not familiar what that design pattern yet then, I hope, this demonstration will encourage you to get familiar with monads and functional programming. Software Developer