@@ -204,96 +204,172 @@ for :mod:`contextvars` support in :mod:`asyncio`.
204204 Cross-calling
205205---------------
206206
207- Calling asyncio from Trio
208- +++++++++++++++++++++++++
207+ First, a bit of background.
209208
210- Pass the function and any arguments to `` loop.run_asyncio() ``. This method
211- conforms to Trio's standard task semantics.
209+ For historical reasons, running an async function (of any
210+ flavor) with Python is a two-step process – that is, given ::
212211
213- :func: ` trio_asyncio.run_asyncio ` is a shortcut for
214- `` asyncio.get_event_loop().run_asyncio ``.
212+ async def proc():
213+ whatever()
215214
216- : :
215+ a call to `` await proc() `` does two things :
217216
218- async def some_asyncio_code(foo):
219- await asyncio.sleep(1)
220- return foo*20
221-
222- res = await trio_asyncio.run_asyncio(some_asyncio_code, 21)
223- assert res == 420
217+ * ``proc() `` creates an ``awaitable ``, i.e. something that has an
218+ ``__await__ `` method.
224219
225- .. autodoc: trio_asyncio.run_asyncio
220+ * ``await proc() `` thus iterates this awaitable until it ends,
221+ supported by your event loop's runtime system.
226222
227- If you already have a coroutine you need to await, call ``loop.run_coroutine() ``.
223+ :mod: `asyncio ` traditionally uses awaitables for indirect procedure calls,
224+ so you often see the pattern::
228225
229- :func: `trio_asyncio.run_coroutine ` is a shortcut for
230- ``asyncio.get_event_loop().run_coroutine ``.
226+ async def some_code():
227+ pass
228+ async def run(proc):
229+ await proc
230+ await run(some_code())
231231
232- ::
232+ This method has a problem: it decouples creating the awailable from running
233+ it. If you decide to add code to ``run `` that retries running ``proc `` when
234+ it encounters a specific error, you're out of luck.
233235
234- async def some_asyncio_code(foo):
235- await asyncio.sleep(1)
236- return foo*20
237-
238- fut = asyncio.ensure_future(some_asyncio_code(21))
239- res = await trio.run_coroutine(fut)
240- assert res == 420
236+ Trio, in contrast, uses (async) callables::
241237
242- .. autodoc: trio_asyncio.run_coroutine
238+ async def some_code():
239+ pass
240+ async def run(proc):
241+ await proc()
242+ await run(some_code)
243243
244- You can also use the `` trio2aio `` function decorator::
244+ Here, calling `` proc `` multiple times is not a problem.
245245
246- @trio2aio
247- async def some_asyncio_code(self, foo):
248- await asyncio.sleep(1)
249- return foo+33
246+ :mod: `trio_asyncio ` adheres to Trio conventions, but the
247+ :mod: `asyncio ` way is also supported when possible.
250248
251- # then, within a trio function
252- res = await some_asyncio_code(9)
253- assert res == 42
249+ Calling asyncio from Trio
250+ +++++++++++++++++++++++++
254251
255- .. autodoc: trio_asyncio.trio2aio
252+ Wrap the callable, awaitable, generator, or iterator in :func: ` aio_as_trio `.
256253
257- If you already have a future, you can wait for it directly.
254+ Thus, you can call an :mod: ` asyncio ` function from :mod: ` trio ` thus::
258255
259- .. autodoc: trio_asyncio.run_future
256+ async def aio_sleep(sec=1):
257+ await asyncio.sleep(sec)
258+ async def trio_sleep(sec=2):
259+ await aio_as_trio(aio_sleep)(sec)
260+ trio_asyncio.run(trio_sleep)(3)
260261
261- :func: `trio_asyncio.run_future ` does not require a running trio-asyncio
262- main loop.
262+ or pre-wrapped::
263263
264- If you need to call an asyncio-ish async context manager from Trio,
265- ``loop.run_asyncio() `` also works::
264+ @aio_as_trio
265+ async def aio_sleep(sec=1):
266+ await asyncio.sleep(sec)
267+ async def trio_sleep(sec=2):
268+ await aio_sleep(sec)
269+ trio_asyncio.run(trio_sleep, 3)
266270
267- async with loop.run_asyncio(generate_context()) as ctx:
268- await loop.run_asyncio(ctx.do_whatever)
271+ or as an awaitable::
269272
270- As you can see from this example, the context that's returned is a "native"
271- asyncio context, so you still need to use ``run_asyncio() `` if you call its
272- methods.
273+ async def aio_sleep(sec=1):
274+ await asyncio.sleep(sec)
275+ async def trio_sleep(sec=2):
276+ await aio_as_trio(aio_sleep(sec))
277+ trio_asyncio.run(trio_sleep, 3)
273278
274- Wrapping an async iterator also works ::
279+ This also works with :mod: ` asyncio ` Futures ::
275280
276- async def slow_nums():
281+ async def aio_sleep(sec=1):
282+ await asyncio.sleep(sec)
283+ return 42
284+ async def trio_sleep(sec=2):
285+ f = aio_sleep(1)
286+ f = asyncio.ensure_future(f)
287+ r = await aio_as_trio(f)
288+ assert r == 42
289+ trio_asyncio.run(trio_sleep, 3)
290+
291+ You can wrap context handlers::
292+
293+ class AsyncCtx:
294+ async def __aenter__(self):
295+ await asyncio.sleep(1)
296+ return self
297+ async def __aexit__(self, *tb):
298+ await asyncio.sleep(1)
299+ async def delay(self, sec=1):
300+ await asyncio.sleep(sec)
301+ async def trio_ctx():
302+ async with aio_as_trio(AsyncCtx()) as ctx:
303+ print("within")
304+ await aio_as_trio(ctx.delay)(2)
305+ trio_asyncio.run(trio_ctx)
306+
307+ As you can see, while :func: `aio_as_trio ` trio-izes the actual context,
308+ it doesn't know about the context's methods. You still need to treat them
309+ as :mod: `asyncio ` methods and wrap them appropriately when you call them.
310+
311+ Note that *creating * the async context handler is not itself an
312+ asynchronous process, i.e. ``AsyncCtx.__init__ `` is a normal
313+ synchronous procedure. Only the actual context handlers (the ``__aenter__ ``
314+ and ``__aexit__ `` methods) are asynchronous. This is why you need to wrap
315+ the context handler itself – unlike simple procedure calls, you cannot wrap
316+ the call for generating the context handler.
317+
318+ Thus, the following code **will not work **::
319+
320+ async def trio_ctx():
321+ async with aio_as_trio(AsyncCtx)() as ctx:
322+ print("within")
323+
324+ You can also wrap async generators or iterators::
325+
326+ async def aio_slow():
277327 n = 0
278328 while True:
279- asyncio.sleep(1 )
329+ await asyncio.sleep(n )
280330 yield n
281331 n += 1
332+ async def printer():
333+ async for n in aio_as_trio(aio_slow()):
334+ print(n)
335+ trio_asyncio.run(printer)
336+
337+ As above, *creating * the async iterator is not itself an asynchronous
338+ process, i.e. the ``__aiter__ `` method that creates the generator or
339+ iterator is a normal synchronous procedure. Only the actual iteration
340+ step (the iterator's ``__anext__ `` method) is asynchronous. Again, you need
341+ to wrap the iterator, not the code creating it – the following code
342+ **will not work **::
282343
283- async def trio_code(loop ):
284- async for n in loop.run_asyncio(slow_nums() ):
344+ async def printer( ):
345+ async for n in aio_as_trio(aio_slow)( ):
285346 print(n)
286347
287- trio_asyncio.run(trio_code)
348+ .. autodoc: trio_asyncio.aio_as_trio
288349
289- Note that in this case we're wrapping an async iterator, i.e. the object
290- returned by calling ``slow_nums ``, not the function itself.
291350
292351 Too complicated?
293- ++++++++++++++++
352+ ----------------
353+
354+ There's also a somewhat-magic wrapper (:func: `allow_asyncio `) which,
355+ as the name implies, allows you to directly call :mod: `asyncio ` functions.
356+ It also works for awaiting futures and for using generators or context
357+ managers::
358+
359+ async def hybrid():
360+ await trio.sleep(1)
361+ await asyncio.sleep(1)
362+ print("Well, that worked")
363+ trio_asyncio.run(trio_asyncio.allow_asyncio, hybrid)
364+
365+ This method works for one-off code. However, there are a couple of
366+ semantic differences between :mod: `asyncio ` and :mod: `trio ` which
367+ :func: `allow_asyncio ` is unable to account for.
368+
369+ Worse, :func: `allow_asyncio ` can be used to auto-adapt asyncio code to Trio
370+ callers, but not vice versa.
294371
295- There's also a somewhat-magic wrapper which allows you to directly call
296- :mod: `asyncio ` functions from :mod: `trio `.
372+ Thus, you really should not use it for "real" programs or libraries.
297373
298374.. autodoc: trio_asyncio.allow_asyncio
299375
@@ -387,10 +463,10 @@ Cancellations are also propagated whenever possible. This means
387463* when the task started with ``run_trio() `` is cancelled,
388464 the future gets cancelled
389465
390- * the future used in ``run_future () `` is cancelled when the Trio code
466+ * the future used in ``run_aio_future () `` is cancelled when the Trio code
391467 calling it is cancelled
392468
393- * However, when the future passed to ``run_future () `` is cancelled (i.e.
469+ * However, when the future passed to ``run_aio_future () `` is cancelled (i.e.
394470 when the task associated with it raises ``asyncio.CancelledError ``), that
395471 exception is passed along unchanged.
396472
0 commit comments