A non-loop thread can submit a coroutine to an already-running event loop and wait on a concurrent future.
Make this comfortable
Python run_coroutine_threadsafe: submit to a live loop and own shutdown
Operation contract
A dedicated worker thread runs one local event loop. The main thread submits a short receipt calculation through run_coroutine_threadsafe, waits for the result with a timeout, then signals loop.stop, joins the worker, and closes the loop. No network transport is opened.
Failure boundary
A result timeout does not automatically stop the coroutine. A production caller must decide whether to cancel the returned future and wait for task cleanup. The fixture waits for the known short result before stopping the loop, so it leaves no task pending at close.
Working program
import asyncio
from threading import Event, Thread
async def calculate_receipt_total():
return 47 + 26
loop = asyncio.new_event_loop()
ready = Event()
def run_receipt_loop():
asyncio.set_event_loop(loop)
ready.set()
loop.run_forever()
worker = Thread(target=run_receipt_loop)
worker.start()
try:
if not ready.wait(timeout=2):
raise TimeoutError("loop did not start")
result = asyncio.run_coroutine_threadsafe(calculate_receipt_total(), loop)
print("total", result.result(timeout=2))
finally:
loop.call_soon_threadsafe(loop.stop)
worker.join(timeout=2)
if worker.is_alive():
raise TimeoutError("loop did not stop")
loop.close()Output
total 73Costs and limits
The thread and event loop have startup and teardown costs that dominate one addition. Keep a loop alive only for an owned workload, and bound the number of submitted tasks.
Common Mistakes
- Do not call result() on the loop thread; it would block the loop.
- A timeout on the concurrent future is not automatic task cancellation.
- Stop producers and finish tasks before closing the loop.
Connected lessons
python
run-coroutine-from-thread
