Introduction
In the previous chapters, you learned what a thread is, how to use threads, and why we need a thread pool. Using a thread pool allows us to save operating system resources at the cost of reducing a parallelism degree. We can think of a thread pool as an abstraction layer that hides details of thread usage from a programmer, allowing us to concentrate on a program's logic rather than on threading issues.
However, using a thread pool is complicated as well. There is no easy way to get a result from a thread pool worker thread. We need to implement our own way to get a result back, and in case of an exception, we have to propagate it to the original thread properly. Besides this, there is no easy way to create a set of dependent asynchronous actions, where one action runs after another finishes its work.
There were several attempts to work around these issues, which resulted in the creation of the Asynchronous Programming Model and the Event-based Asynchronous Pattern, mentioned...