asyncio interop: tractor.to_asyncio#
“Infected asyncio” mode: spawn an actor with
start_actor(..., infect_asyncio=True) and its process runs
trio as a guest on top of the asyncio loop —
letting your trio task tree drive asyncio tasks in the same
process while the rest of the actor tree stays pure trio. Each
trio <-> asyncio task pair is linked with structured concurrency
(SC) semantics: error or cancellation on either side tears down
both, with the cause translated cross-loop.
A trio guest driving asyncio tasks in one actor.#
See examples/infected_asyncio_echo_server.py for a complete
worked example.
Starting asyncio tasks from trio#
- tractor.to_asyncio.open_channel_from(target, suppress_graceful_exits=True, **target_kwargs)[source]#
Start an asyncio.Task as target() and open an inter-loop (linked) channel for streaming between it and the current trio.Task.
A pair (chan: LinkedTaskChannel, Any) is delivered to the caller where the 2nd element is the value provided by the asyncio.Task’s unblocking call to chan.started_nowait().
- Parameters:
- Return type:
- async tractor.to_asyncio.run_task(func, *, qsize=1024, **kwargs)[source]#
Run an asyncio-compat async function or generator in a task, return or stream the result back to trio.
Note
open_channel_from() mirrors the
Portal.open_context() handshake: the asyncio side calls
chan.started_nowait(value) and that value pops out as
first on the trio side. run_task() is the one-shot
form — run a single asyncio-compatible coroutine fn and return
its result to trio.
The inter-loop channel#
- class tractor.to_asyncio.LinkedTaskChannel(_to_aio, _from_aio, _to_trio, _trio_cs, _trio_task, _aio_task_complete, _closed_by_aio_task=False, _suppress_graceful_exits=True, _trio_err=None, _trio_to_raise=None, _trio_exited=False, _aio_task=None, _aio_err=None, _aio_to_raise=None, _aio_result=<class 'tractor._context.Unresolved'>, _broadcaster=None)[source]#
A “linked task channel” which allows for two-way synchronized msg passing between a
trio-in-guest-mode task and anasynciotask scheduled in the host loop.- Parameters:
_to_aio (asyncio.Queue)
_from_aio (trio.MemoryReceiveChannel)
_to_trio (trio.MemorySendChannel)
_trio_cs (trio.CancelScope)
_trio_task (trio.Task)
_aio_task_complete (trio.Event)
_closed_by_aio_task (bool)
_suppress_graceful_exits (bool)
_trio_err (BaseException | None)
_trio_to_raise (AsyncioTaskExited | AsyncioCancelled | BaseException | None)
_trio_exited (bool)
_aio_task (asyncio.Task | None)
_aio_err (BaseException | None)
_aio_to_raise (TrioTaskExited | BaseException | None)
_aio_result (Any | Unresolved)
_broadcaster (BroadcastReceiver | None)
- started_nowait(val=None)[source]#
Synchronize aio-side with its trio-parent.
- Parameters:
val (Any)
- Return type:
None
- async receive()[source]#
Receive a value trio.Task <- asyncio.Task.
Note the tasks in each loop are “SC linked” as a pair with exception/cancel handling to teardown both sides on any unexpected error or cancellation.
- Return type:
- async get()[source]#
Receive a value asyncio.Task <- trio.Task.
This is equiv to await self._to_aio.get().
- Return type:
- async send(item)[source]#
Send a value trio.Task -> asyncio.Task by enqueuing item onto the internal asyncio.Queue via put_nowait().
- Parameters:
item (Any)
- Return type:
None
- send_nowait(item)[source]#
Send a value through FROM the asyncio.Task to the trio.Task NON-BLOCKING.
This is equiv to self._to_trio.send_nowait().
- Parameters:
item (Any)
- Return type:
None
Note
The trio side uses the async API
(LinkedTaskChannel.send() /
LinkedTaskChannel.receive()); the asyncio side uses the
loop-safe sync/await mix
(LinkedTaskChannel.send_nowait() /
LinkedTaskChannel.get() /
LinkedTaskChannel.started_nowait()).
Translated exception types#
Cross-loop failures are re-raised on the other side as one of these explicit translation types, so you always know which loop actually died first:
- exception tractor._exceptions.AsyncioCancelled[source]#
Asyncio cancelled translation (non-base) error for use with the
to_asynciomodule to be raised in thetrioside taskNOTE: this should NOT inherit from asyncio.CancelledError or tests should break!
- exception tractor._exceptions.AsyncioTaskExited[source]#
asyncio.Task “exited” translation error for use with the to_asyncio APIs to be raised in the trio side task indicating on .run_task()/.open_channel_from() exit that the aio side exited early/silently.
- exception tractor._exceptions.TrioCancelled[source]#
Trio cancelled translation (non-base) error for use with the to_asyncio module to be raised in the asyncio.Task to indicate that the trio side raised Cancelled or an error.
- exception tractor._exceptions.TrioTaskExited[source]#
The trio-side task exited without explicitly cancelling the asyncio.Task peer.
This is very similar to how trio.ClosedResource acts as a “clean shutdown” signal to the consumer side of a mem-chan,
https://trio.readthedocs.io/en/stable/reference-core.html#clean-shutdown-with-channels
- exception tractor.to_asyncio.AsyncioRuntimeTranslationError[source]#
Bases:
RuntimeErrorWe failed to correctly relay runtime semantics and/or maintain SC supervision rules cross-event-loop.
Guest-mode entrypoint#
run_as_asyncio_guest() is the runtime-internal entrypoint that
boots trio in guest mode inside an infected actor — you get it
implicitly via infect_asyncio=True and shouldn’t need to call
it yourself.
See also
Runtime and spawning for the infect_asyncio spawn flag,
tractor.Actor.is_infected_aio() for runtime
introspection, Debugging and devx: tractor.devx for using the debugger from
inside asyncio tasks, and Infected asyncio for the
guided tour.