Un gestionnaire de contexte Async est un objet qui définit des méthodes asynchrones __aenter__ et __aexit__ et est utilisé dans la construction async with. Ce gestionnaire est nécessaire pour ouvrir/fermer correctement des ressources dans des fonctions asynchrones : connexions à des bases de données, fichiers, sessions, etc.
Exemple de mise en œuvre :
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')
Le contexte asynchrone permet de ne pas bloquer la boucle d'événements avec des délais techniques, augmentant ainsi les performances des programmes concurrents.
Peut-on utiliser un simple with à l'intérieur d'un async def ?
Réponse : Oui, mais si les opérations à l'intérieur du gestionnaire de contexte utilisent awaitable (nécessitent await), il faut utiliser exactemnt async with. Le simple with ne prend pas en charge l'entrée/sortie asynchrone, il bloquera la boucle d'événements ou échouera si await est appelé à l'intérieur de enter/exit.
Exemple :
async def foo(): with open('file.txt') as f: # c'est ok, la lecture est synchrone data = f.read() # Mais il n'est pas possible d'attendre à l'intérieur d'un contexte normal, seulement à l'intérieur de async with
Histoire
Projet : Service web avec API asynchrone.
Problème : Le gestionnaire de connexion à la base utilisait un simple with, mais un await a été appelé à l'intérieur. Cela a conduit à l'erreur RuntimeError("Cannot use 'await' outside async function") et à des blocages de la boucle d'événements en production.
Histoire
Projet : Chat sur Websockets.
Problème : Lors du travail avec des connexions, les ressources websocket n'étaient pas fermées (utilisation d'un gestionnaire synchrone), ce qui entraînait des fuites de mémoire et des connexions suspendues.
Histoire
Projet : File d'attente de tâches asynchrone multithread.
Problème : Le gestionnaire de tâches a mal implémenté
__aexit__, oubliant de retourner un awaitable. En conséquence, l'achèvement des tâches n'était pas garanti, et une partie des tâches "suspendait" dans le système.