Concurrency in Python with Async Await
python
Concurrency in python has become incredibly simple since the asyncio
package was created. Any developer, with a small restructuring of flow and an extra couple of keywords, can create easily concurrent applications. With the addition of multiple processes, this can easily become parallel too with the help of the multiprocessing lib.
Below is a simple demo of a task that could include an IO-bound operation where the application waits on another process. There are a list of tasks and a simple execution in both synchronous and asynchronous fashion.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
import asyncio import time class MyTask: duration: int name: str def __init__(self, name: str, duration: int = None): self.name = name self.duration = duration async def do_task(t: MyTask): print(f'>>> doing task {t.name}') if t.duration: print(f'found wait of duration {t.duration} seconds') await asyncio.sleep(t.duration) print(f'<<< finished task {t.name}') async def main(): tasks = [ MyTask(name="one", duration=1), MyTask(name="two"), MyTask(name="three", duration=3), ] # sync start = time.perf_counter() for task in tasks: await do_task(task) print(f'took {time.perf_counter() - start} seconds') print("-"*20) # async start = time.perf_counter() await asyncio.wait([ do_task(task) for task in tasks ]) print(f'took {time.perf_counter() - start} seconds') if __name__ == '__main__': asyncio.run(main())
This prints out the below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
>>> doing task one found wait of duration 1 seconds <<< finished task one >>> doing task two <<< finished task two >>> doing task three found wait of duration 3 seconds <<< finished task three took 4.0182585 seconds -------------------- >>> doing task three found wait of duration 3 seconds >>> doing task two <<< finished task two >>> doing task one found wait of duration 1 seconds <<< finished task one <<< finished task three took 3.0027220999999997 seconds
As we can see by running the waits as concurrent events we take the execution time + longest wait as opposed to the sum of the waits. Just by leveraging some of the power that asyncio
provides, we can remove the majority of external processing waiting from our synchronous code. There are also options to fine-tune how these primitive awaitables are collected and when execution is handed back to the main process.