Skip to content

Commit 6690376

Browse files
committed
Update usage doc
#11
1 parent c692554 commit 6690376

2 files changed

Lines changed: 92 additions & 49 deletions

File tree

docs/source/usage.rst

Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -34,36 +34,72 @@ After::
3434
3535
trio_asyncio.run(async_main, *args)
3636

37-
Equivalently, wrap your main loop in a :func:`trio_asyncio.open_loop` call ::
37+
Equivalently, wrap your main loop (or any other code that needs to talk to
38+
asyncio) in a :func:`trio_asyncio.open_loop` call ::
3839

40+
import trio
3941
import trio_asyncio
4042

41-
async def async_main(*args):
43+
async def async_main_wrapper(*args):
4244
async with trio_asyncio.open_loop() as loop:
43-
pass # async main code goes here
45+
assert loop == asyncio.get_event_loop()
46+
await async_main(*args)
47+
48+
trio.run(async_main_wrapper, *args)
49+
50+
Within the ``async with`` block, an asyncio mainloop is active.
51+
52+
As this code demonstrates, you don't need to pass the ``loop`` argument
53+
around, as :func:`asyncio.get_event_loop` will retrieve it when you're in
54+
the loop's context.
55+
56+
.. note::
4457

45-
Within the ``async with`` block, the asyncio mainloop is active. You don't
46-
need to pass the ``loop`` argument around, as
47-
:func:`asyncio.get_event_loop` will do the right thing.
58+
Don't do both. The following code **will not work**::
59+
60+
import trio
61+
import trio_asyncio
62+
63+
async def async_main_wrapper(*args):
64+
async with trio_asyncio.open_loop() as loop:
65+
await async_main(*args)
66+
67+
trio_asyncio.run(async_main_wrapper, *args)
4868

4969
.. autofunction:: trio_asyncio.open_loop
5070

5171
.. autofunction:: trio_asyncio.run
5272

73+
.. note::
74+
75+
The ``async with open_loop()`` way of running ``trio_asyncio`` is
76+
intended to transparently allow a library to use ``asyncio`` code,
77+
supported by a "local" asyncio loop, without affecting the rest of your
78+
Trio program.
79+
80+
However, currently this doesn't work because Trio does not yet support
81+
``contextvars``. Progress on this limitation is tracked in `this issue
82+
on github <https://github.com/python-trio/trio-asyncio/issues/9>`_.
83+
5384
Stopping
5485
--------
5586

5687
The asyncio mainloop will be stopped automatically when the code within
57-
``async with open_loop()`` exits. Trio-asyncio will process all outstanding
58-
callbacks and terminate. As in asyncio, callbacks which are added during
59-
this step will be ignored.
88+
``async with open_loop()`` / ``trio_asyncion.run()`` exits. Trio-asyncio
89+
will process all outstanding callbacks and terminate. As in asyncio,
90+
callbacks which are added during this step will be ignored.
6091

6192
You cannot restart the loop, nor would you want to.
6293

63-
Asyncio main loop
64-
+++++++++++++++++
94+
Asyncio main loop.
95+
++++++++++++++++++
96+
97+
Short answer: don't.
98+
99+
.. _native-loop:
65100

66-
Well …
101+
Native Mode
102+
-----------
67103

68104
What you really want to do is to use a Trio main loop, and run your asyncio
69105
code in its context. In other words, you should transform this code::
@@ -75,35 +111,30 @@ code in its context. In other words, you should transform this code::
75111
to this::
76112

77113
async def trio_main():
78-
async with trio_asyncio.open_loop() as loop:
79-
await loop.run_asyncio(async_main)
114+
await loop.run_asyncio(async_main)
80115

81116
def main():
82-
trio.run(trio_main)
83-
84-
You don't need to pass around the ``loop`` argument since trio remembers it
85-
in its task structure: ``asyncio.get_event_loop()`` always works while
86-
your program is executing an ``async with open_loop():`` block.
87-
88-
There is no Trio equivalent to ``loop.run_forever()``. The loop terminates
89-
when you leave the ``async with`` block; it cannot be halted or restarted.
117+
trio_asyncio.run(trio_main)
90118

91-
This mode is called an "async loop" or "asynchronous loop" because it is
92-
started from an async (Trio) context.
119+
Beside this, no changes to your code are required.
93120

94-
Compatibility mode
121+
Compatibility Mode
95122
------------------
96123

97124
You still can do things "the asyncio way": the to-be-replaced code from the
98-
previous section still works. However, behind the scenes
99-
a separate thread executes the Trio main loop. It runs in lock-step with
100-
the thread that calls ``loop.run_forever()`` or
101-
``loop.run_until_complete(coro)``. Signals etc. get
102-
delegated if possible (except for [SIGCHLD]_). Thus, there should be no
103-
concurrency issues.
125+
:ref:`previosu section <native-loop>`
126+
still works – or at least it attempts to work.
104127

105-
Caveat: you may still experience problems, particularly if your code (or
106-
a library you're calling) does not expect to run in a different thread.
128+
.. warning::
129+
130+
tl;dr: Don't use Compatibility Mode in production code.
131+
132+
However, this is only possible because this mode starts a separate thread
133+
which executes the asyncio main
134+
loop. It runs in lock-step with the code that calls ``loop.run_forever()``
135+
or ``loop.run_until_complete(coro)``. Signals etc. get
136+
delegated if possible (except for [SIGCHLD]_). Thus, while there should be no
137+
concurrency issues, you may still experience hard-to-debug problems.
107138

108139
.. [SIGCHLD] Python requires you to register SIGCHLD handlers in the main
109140
thread, but doesn't run them at all when waiting for another thread.
@@ -116,20 +147,18 @@ a library you're calling) does not expect to run in a different thread.
116147
with another call to ``loop.run_forever()`` or ``loop.run_until_complete(coro)``,
117148
just as with a regular asyncio loop.
118149

119-
This mode is called a "sync loop" or "synchronous loop" because it is
120-
started and used from a traditional synchronous Python context.
150+
If you use a compatibility-mode loop in a separate thread, you *must* stop and close it
151+
before terminating that thread. Otherwise your thread will leak resources.
121152

122-
If you use a sync loop in a separate thread, you *must* stop and close it
123-
before terminating the thread. Otherwise your thread will leak resources.
153+
.. note::
124154

125-
.. warning::
126155
Compatibility mode has been added to verify that various test suites,
127-
most notably the one from asyncio itself, continue to work. In a
156+
most notably the tests from asyncio itself, continue to work. In a
128157
real-world program with a long-running asyncio mainloop, you *really*
129-
want to use a :ref:`Trio mainloop <trio-loop>` instead.
158+
want to use a :ref:`native-mode main loop <native-loop>` instead.
130159

131-
The authors reserve the right to not fix compatibility mode bugs if that
132-
would negatively impact trio-asyncio's core functions.
160+
The authors reserve the right to not fix compatibility mode bugs, or
161+
even to remove compatibility mode entirely.
133162

134163
.. autoclass:: trio_asyncio.sync.SyncTrioEventLoop
135164

trio_asyncio/adapter.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,44 @@
66
# import logging
77
# logger = logging.getLogger(__name__)
88

9-
from functools import wraps
9+
from functools import wraps, partial
1010

1111
__all__ = ['trio2aio', 'aio2trio']
1212

1313

1414
def trio2aio(proc):
15+
"""Decorate an asyncio function so that it's callable by Trio (only)."""
1516
@wraps(proc)
16-
async def call(*args):
17-
return await trio_asyncio.run_asyncio(proc, *args)
17+
async def call(*args, **kwargs):
18+
if kwargs:
19+
proc_ = partial(proc, **kwargs)
20+
else:
21+
proc_ = proc
22+
return await trio_asyncio.run_asyncio(proc_, *args)
1823

1924
return call
2025

2126

2227
def aio2trio(proc):
28+
"""Decorate a Trio function so that it's callable by asyncio (only)."""
2329
@wraps(proc)
24-
async def call(*args):
25-
return await trio_asyncio.run_trio(proc, *args)
30+
async def call(*args, **kwargs):
31+
if kwargs:
32+
proc_ = partial(proc, **kwargs)
33+
else:
34+
proc_ = proc
35+
return await trio_asyncio.run_trio(proc_, *args)
2636

2737
return call
2838

2939

3040
def aio2trio_task(proc):
3141
@wraps(proc)
32-
async def call(*args):
33-
trio_asyncio.run_trio_task(proc, *args)
42+
async def call(*args, **kwargs):
43+
if kwargs:
44+
proc_ = partial(proc, **kwargs)
45+
else:
46+
proc_ = proc
47+
trio_asyncio.run_trio_task(proc_, *args)
3448

3549
return call

0 commit comments

Comments
 (0)