I would say, not a pyramid , but a chain .
The chain of asynchronous calls is not just normal: the only way an asynchronous code should be.
Normal threads, when you suspend them to wait for the end of an I / O operation, will simply take up resources. If there are 1000 calls to your web server at the same time and you send 1000 requests to the database, you will have exactly 1000 threads stopped. For each thread, the OS will allocate 1 MB of RAM, and the thread scheduler will take into account each of them, which will slow down the speed of the system as a whole.
Instead, Windows offers special threads: sleepy . Such a thread sleeps, but can be awakened by the system when it is necessary to process the result of an I / O operation. After processing the result, the thread falls asleep again.
Processing the results should not be very difficult. Usually this is something quite simple: received a sample from the database, generated HTML and gave it to the client. With this approach, the number of threads can be very small, for example, for the same 1000 clients, 10 threads may be enough, because most of the time they expect a response from the database, and when it is received, they quickly generate HTML and are ready to process the next answer.
The requirement of simple processing is very important. If you delay the flow with your processing, the task scheduler will have to create a new thread.
Simple processing is: 1. Fast calculations. If you need to count for a long time, try counting in parallel, or transfer processing to a background process. 1. No thread synchronization. No semaphores, mutexes, monitors, and more. The stream waiting for a signal will be in a normal sleep state, but in a non-sensitive state. 1. No waiting for I / O operations. Such a stream will also sleep the usual sleep.
That is why async
methods need to be chained. In essence, this will mean that from the deepest method in the code there is only waiting for I / O and simple processing of the results.
If the last method in the chain is very simple and does not contain an I / O operation, you can use Task.CompletedTask
or Task.FromResult
.