Introduction
In the previous chapters, we 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 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 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 those issues, which resulted in the creation of Asynchronous Programming Model and Event-based Asynchronous Pattern, mentioned in Chapter...