Guest Mode¶
Source code: Lib/asyncio/guest.py
Running asyncio as a Guest in Another Event Loop¶
Guest mode allows asyncio to run cooperatively inside a host event loop such as a GUI toolkit’s main loop (Tkinter, Qt, GTK, etc.). Instead of replacing the host loop, asyncio piggybacks on it:
The host thread keeps running its own main loop as usual.
A background I/O thread blocks on the selector (I/O polling). When I/O events arrive it hands them back to the host thread via a thread-safe callback. The thread is not a daemon thread; it is joined when the guest run finishes.
The host thread then runs
loop.process_events()andloop.process_ready()to advance the asyncio event loop by one step, then signals the I/O thread to poll again.
Exactly one of the two threads touches the event loop at any moment, so neither the host loop nor the asyncio loop starves the other.
Typical use cases:
Incrementally migrating a Tkinter/Qt/GTK application to
async/awaitwithout replacing the native event loop.Embedding asyncio I/O (HTTP clients, websockets, …) inside a GUI app.
Running asyncio alongside a framework that owns the main thread.
Example
See Doc/includes/asyncio_guest_tkinter.py for a complete Tkinter
example that embeds asyncio inside tkinter.mainloop() using
start_guest_run().
See also
The asyncio-guest project — the proof of concept this feature is based on — has runnable examples for many more hosts: Tkinter, Qt (PyQt5/PySide6), GTK, pygame, Win32 and Tornado.
Trio’s guest mode, which pioneered this approach.
API
- asyncio.start_guest_run(async_fn, *args, run_sync_soon_threadsafe, done_callback)¶
Run async_fn as a guest inside another event loop.
Must be called from the host event loop’s thread. The host loop (e.g.
tkinter.mainloop()) remains in control of that thread; asyncio I/O polling runs in a background non-daemon thread that is joined when the run finishes.- Parameters:
async_fn – The async function to run as the top-level coroutine.
args – Positional arguments forwarded to async_fn.
run_sync_soon_threadsafe – A callable that schedules a zero-argument callable on the host event loop’s thread. It must be thread-safe, must not block, and must not raise; it need not preserve ordering. For Tkinter use a
root.call('after', 'idle', ...)wrapper; for Qt use aQMetaObject.invokeMethodwrapper; etc.done_callback – Called on the host thread after the run has fully finished and the loop is closed (see Lifecycle and Cleanup). Receives the
Taskas its sole argument. Inspect the outcome withTask.result(),Task.exception(), orTask.cancelled().
- Returns:
The
Taskwrapping async_fn.
To cancel the task from the host, use:
loop.call_soon_threadsafe(task.cancel)
This wakes the I/O thread from its selector wait so cancellation is processed promptly.
Added in version 3.16.
Lifecycle and Cleanup¶
For the whole guest run the guest loop is the host thread’s running
loop: get_running_loop() works inside guest tasks,
loop.is_running() returns True, and
starting another event loop on that thread — including a nested
asyncio.run() or loop.run_until_complete() — raises
RuntimeError. Consequently a thread that is already running an
asyncio event loop cannot start a guest run.
When the main task finishes, cleanup equivalent to asyncio.run()
takes place on the host thread: remaining tasks are cancelled,
asynchronous generators and the default executor are shut down, the I/O
thread is joined, and the loop is closed. Only then is done_callback
invoked.
If the interpreter exits while a guest run is unfinished, the run is abandoned: the I/O thread is woken and joined so that interpreter shutdown does not hang, pending tasks are not cancelled, and done_callback is not called.
Signal Handling¶
In guest mode the host owns signal handling:
The guest loop never touches
signal.set_wakeup_fd(), neither to install a file descriptor nor to reset it on close, so the host’s signal wake-up pipeline stays intact.loop.add_signal_handler()andloop.remove_signal_handler()raiseRuntimeError.To let asyncio code react to a signal, catch it in the host (with
signal.signal()or the host framework’s facilities) and forward it into the loop withloop.call_soon_threadsafe().
Host Requirements¶
run_sync_soon_threadsafe must be thread-safe, non-blocking, and must not raise. It may run callbacks in any order.
Host code running outside guest callbacks (for example a GUI button handler) must interact with the loop exclusively through
loop.call_soon_threadsafe(), even though it runs on the loop’s own thread: the I/O thread may be inside the selector, and onlycall_soon_threadsafewakes it safely.loop.stop()is not supported in guest mode.
Low-level Event Loop Methods
start_guest_run() drives the loop through three low-level methods
– loop.poll_events(),
loop.process_events(), and
loop.process_ready() – which
decompose a single iteration of the event loop into independently
callable steps. See Event loop for their reference
documentation.