Martin Kirchgessner
22 mai 2025
Martin Kirchgessner
Pythoniste depuis la v2.4, à temps plein depuis 8 ans.
J'ai bossé avec Python asynchrone sur :
(né en 2008)
    import asyncore
    import asynchat
    class Bot(asynchat.async_chat):
        ...
        def handle_connect(self):
            ...
        def collect_incoming_data(self, data):
            ...
        def found_terminator(self):
            ...
        ...
    bot = Bot("server.irc.net", 6667)
    asyncore.loop()$ python3.12 irc.py
Traceback (most recent call last):
File "/home/martin/irc.py", line 15, in <module>
    import asyncore
    ModuleNotFoundError: No module named 'asyncore'Qu'est-ce qui pourrait mal se passer ?
async/await presque partoutrequests → httpxasync def monde_nouveau()await moralité()The C10K problem , en 1999
You can buy a 1000MHz machine with 2 GB of RAM and a GB Ethernet card [...]
at 20000 clients, that's 50KHz, 100Kbytes, and 50Kbits/sec per client.
ça devrait suffire pour une réponse de 1-5Ko
et donc: NGinx, NodeJS
        while True:
            e = eventQueue.dequeue()
            processEvent(e)
                
                    eventQueue remplie "par l'extérieur"
                
Python is super()"il peut tout faire"
 
                Même des boucles d'évènements.
| Quand | Version | Quoi | 
|---|---|---|
| Avant | 2.x | gevent, Tornado, Twisted, asyncore | 
| 2012 | 3.3 | PEP3156 module asyncio | 
| 2015 | 3.5 | PEP492 async/await | 
| 2018 | ASGI | 
import asyncio
async def on_connect(reader, writer):
    line = await reader.readline()
    writer.write(line)
    await writer.drain()
async def main(host, port):
    srv = await asyncio.start_server(on_connect, host, port)
    await srv.serve_forever()
asyncio.run(main('127.0.0.1', 8888))
                (peut inclure votre app WSGI)
Concurrence, synchro, verrous, ...
 
                
             
                async def monde_nouveau()__main__.py
                    asyncio.run(main)
                    plutôt que main()
                
Ou bien...
                    loop.run_until_complete(main),
                    loop.call_soon(main),
                    loop.run_forever(main)
                
... utilisez un framework !
 
            
async def ma_fonction():
    x = 1
    y = await service.envoi(x)
    return y
                
y = await ma_fonction()
                
coroutine = ma_fonction() # ⚠️
                Coroutine[YieldType, SendType, ReturnType]
                async def ma_fonction() -> str:
    return "restons simples"
y = await ma_fonction()if await fut() + 1:
    ...
pair = await fut(), 'spam'
await foo()['spam'].baz()()if (await fut()) + 1:
    ...
pair = (await fut()), 'spam'
await ( foo()['spam'].baz()() )def order(request):
    order = db.save_order(request)
    order.init_packaging()
    order.email_confirmation()
    return templates.confirm(order)async def order(request):
    order = await db.save_order(request)
    app.add_background_task(order.init_packaging())
    app.add_background_task(order.email_confirmation())
    return templates.confirm(order)from quart import websocket
@app.websocket('/echo')
async def echo_ws():
    while True:
        data = await websocket.receive()
        await websocket.send(data)async def cancel_me():
    print('cancel_me(): before sleep')
    try:
        # Wait for 1 hour
        await asyncio.sleep(3600)
    except asyncio.CancelledError:
        print('cancel_me(): cancel sleep')
        raise
    finally:
        print('cancel_me(): after sleep')async def iter_stream():
    async for item in OnlineCollection():
        item.truc()class OnlineCollection:
    async def __anext__(self):
        if data := await self.fetch_data():
            return data
        else:
            raise StopAsyncIteration()async def wait_for_context():
    async with OnlineContext() as contexte:
        contexte.truc()class OnlineContext:
    async def __aenter__(self):
        await distant_logger.ping()
    async def __aexit__(self, exc_type, exc, tb):
        if exc:
            await distant_logger.oops(exc)
        else:
            await distant_logger.pong()
                
                    Evitez de yield sous un with
                
                    PEP 789
                    pourrait introduire with sys.prevent_yields(reason)
                    dans Python 3.14
                
PYTHONASYNCIODEBUG=1 python mon_appli.py
# OU
asyncio.run(..., debug=True)
# OU
loop.set_debug()Essentiel pour voir:
awaitpip install aiomonitor
                async def wrapped_main():
    loop = asyncio.get_running_loop()
    with aiomonitor.start_monitor(loop):
        await real_main()
                    loop.run_in_executor(fonction_synchrone)
                
Il reste le GIL, aussi.
⚠️ asyncio n'est pas thread-safe ⚠️
⚠️ attendre le réseau, pas le disque ⚠️
Par exemple aiosqlite fait tout dans un thread.
                    FastAPI/Quart
+ uvicorn/hypercorn
                
                    pytest_asyncio c'est rock'n'roll
                
await moralité()Petite appli Quart/SQLite
~80 req/s dans 200Mb de RAM
+ un upload de 2GB de même temps:Télécharger 12000 petits objets depuis S3
session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
    pool_connections=200, pool_maxsize=200
)
session.mount("https://", adapter)
executor = concurrent.futures.ThreadPoolExecutor(max_workers=200)
    for _ in executor.map(session.get, urls_generator()):
        pass Télécharger 12000 petits objets depuis S3
tasks = []
for url in urls_generator():
    tasks.append(asyncio.create_task(aiohttp.get(url)))
asyncio.gather(tasks)Les deux implem ne sont pas à 100% de CPU 🤔
Mais, résistant à la charge
Vous avez déjà une appli ... 🤷
Vous vous souvenez du bot du début ?
                    L'équivalent "moderne" de asynchat sont les
                    
                        Transport/Protocol
                    
                
Une appli WSGI ça peut suffire aussi.
Merci d'avoir suivi ! Les slides sont sur https://mkir.ch