AsyncIO
It is convenient to process I/O intensitve operation concurrently. If a method spends time waiting for response from I/O, we can safely assume that it is not doing anything useful and it can yield execution. In asynchronous programming, the event loop suspends the waiting task and registers interest in its I/O operation with the operating system.
When the OS signals that the I/O is ready, the event loop resumes the task. In Python asyncio
the execution of task execution is handled by a single main thread concurrently, not in parallel. For I/O-bound workloads this simplifies the execution and often can perform better than in parallel as it does not need to coordinate thread execution.
Asyncio is used to build a high level concurrency framework couroutines
Async Hello World
Two keywords async
and await
are at the core of the coroutines.
async def foo()
-async
keyword in front of the method makes the function awaitable. Calling such method returns a coroutine object, or an async generator if the method is a generator.await
- keyword that suspends the execution of the surrounding coroutine and passes controll to the event loopasyncio.run()
starts an event loop, runs the given coroutine until it completes, and then closes the event loop.
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
if __name__ == '__main__':
asyncio.run(main())
Chaining
Chaining means that a one corouting is awaiting execution of another coroutine. It is common pattern when chaining API calls or fetching records from a database. Below is an example where we asyncronously fetch a user and their orders and once both calls are completed, we return the combined result.
@dataclass
class User:
user_id: int
user_name: str
@dataclass
class UserWithOrders:
user: User
orders: list
async def fetch_orders(user_id: int) -> list:
orders = {
1: [1, 3, 10, 11],
2: [23, 34],
3: [],
}
return orders.get(user_id, [])
async def fetch_user(user_id: int) -> dict:
return User(user_id=user_id, user_name=f"user-{user_id}")
async def get_user_with_orders(user_id: int) -> UserWithOrders:
user, orders = await asyncio.gather(fetch_user(user_id), fetch_orders(user_id))
return UserWithOrders(user=user, orders=orders)
if __name__ == '__main__':
user_w_orders = asyncio.run(get_user_with_orders(1))
assert user_w_orders == UserWithOrders(User(1, "user-1"), [1, 3, 10, 11])