A worker thread must hand a callback to the event loop through a thread-safe scheduling method.
Make this comfortable
Python asyncio loop callbacks: submit from a worker with call_soon_threadsafe
Operation contract
The loop owns an asyncio.Event and a receipt list. A worker thread schedules a callback that appends receipt 47 and sets the event. The coroutine waits for that event, joins the worker after delivery, and reads the loop-owned list.
Failure boundary
The callback is short and runs on the loop thread; it must not perform blocking I/O. The worker keeps a reference to a live loop only for this bounded fixture. In a service, shutdown must stop submissions before closing the loop, and an Event wait needs a deadline.
Working program
import asyncio
from threading import Thread
async def collect_receipt():
loop = asyncio.get_running_loop()
delivered = asyncio.Event()
receipts = []
def accept_on_loop():
receipts.append(47)
delivered.set()
worker = Thread(target=lambda: loop.call_soon_threadsafe(accept_on_loop))
worker.start()
await asyncio.wait_for(delivered.wait(), timeout=2)
worker.join()
print("receipts", receipts)
asyncio.run(collect_receipt())Output
receipts [47]Costs and limits
Scheduling one callback is small, but unbounded worker submissions can still overfill a loop's ready queue. A real producer needs admission control and a shutdown handshake.
Common Mistakes
- Do not call a loop's ordinary call_soon from a different thread.
- Do not run blocking work inside the scheduled callback.
- A closed loop cannot accept new callbacks; stop producers first.
Connected lessons
python
loop-call-soon-threadsafe
