Executors and futures

Before we see how to inject threads or processes in to an asynchronous event loop, we will take a closer look at the concurrent.futures module that will later be the main ingredient of our so-called workaround.

The most important classes in the concurrent.futures module are Executor and Future.

Executor represents a pool of resources that may process work items in parallel. This may seem very similar in purpose to classes from the multiprocessing modulePool and dummy.Poolbut it has a completely different interface and semantics. It is a base class not intended for instantiation and has the following two concrete implementations:

Every executor provides the following three methods:

The most interesting method is submit() because of the Future object it returns. It represents asynchronous execution of the callable and only indirectly represents its result. In order to obtain the actual return value of the submitted callable, you need to call the Future.result() method. And if the callable has already finished, the result() method will not block and will just return the function output. If it is not true, it will block until the result is ready. Treat it like a promise of a result (actually, it is the same concept as a promise in JavaScript). You don't need to unpack it immediately after receiving it (with the result() method), but if you try to do that, it is guaranteed to eventually return something like the following:

>>> def loudly_return():
...     print("processing")
...     return 42
...     
>>> from concurrent.futures import ThreadPoolExecutor
>>> with ThreadPoolExecutor(1) as executor:
...     future = executor.submit(loudly_return)
...     
processing
>>> future
<Future at 0x33cbf98 state=finished returned int>
>>> future.result()
42  

If you want to use the Executor.map() method, it does not differ in usage from the Pool.map() method of the pool class from the multiprocessing module, as follows:

def main(): 
    with ThreadPoolExecutor(POOL_SIZE) as pool: 
        results = pool.map(fetch_rates, BASES) 
 
    for result in results: 
        present_result(*result) 

In the next section, we'll see how to use executors in an event loop.