ProgrammingPython Developer (asyncio/Backend)

What is an Async context manager in Python, how to implement it, and where is it indispensable?

Pass interviews with Hintsage AI assistant

Answer.

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.

Trick question.

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

Real error examples due to ignorance of the subtleties of the topic.


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.