An Async context manager is an object that defines the asynchronous methods __aenter__ and __aexit__ and is used in the async with statement. Such a manager is needed for the correct opening/closing of resources in asynchronous functions: database connections, files, sessions, etc.
Implementation example:
class AsyncDBConnection: async def __aenter__(self): self.conn = await async_db_connect() return self.conn async def __aexit__(self, exc_type, exc, tb): await self.conn.close() async def main(): async with AsyncDBConnection() as conn: await conn.query('SELECT 1')
The asynchronous context allows avoiding blocking the event loop with technical delays, increasing the performance of concurrent programs.
Can you use a regular with inside async def?
Answer: Yes, but if the operations inside the context manager use awaitable (require await), then you need async with. A regular with does not support asynchronous enter/exit, it will block the event loop or fail if await is called inside enter/exit.
Example:
async def foo(): with open('file.txt') as f: # this is ok, reading is synchronous data = f.read() # But you cannot await inside a regular context, only inside async with
Story
Project: Web service with asynchronous API.
Problem: The connection manager to the database used a regular with, but await was called inside it. This led to a RuntimeError("Cannot use 'await' outside async function") and blocked the event loop in production.
Story
Project: Chat on Websockets.
Problem: Websocket resources weren't closed when working with connections (used a regular synchronous manager), leading to memory leaks and hanging connections.
Story
Project: Multithreaded asynchronous task queue.
Problem: The task manager incorrectly implemented
__aexit__, forgetting to return an awaitable. Because of this, task completions were not guaranteed, and some tasks "hung" in the system.