Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a coroutine to AbstractEventLoop.call_later

The following is the code I'm trying to get working:

>>> import asyncio
>>> async def foo(loop, iv):
...     await asyncio.sleep(1, loop=loop)
...     print(f'done: {iv}')
...     
>>> loop = asyncio.get_event_loop()
>>> loop.call_later(2, foo, loop, 10)
<TimerHandle when=36395.554089349 foo(<_UnixSelecto...e debug=False>, 10) at <input>:1>
>>> loop.run_forever()

(Python 3.6)

Basically the foo() function has some chained async calls, so this method has to be async as there is a need to await for the chained calls. However this method is triggered after a delay, and when one runs this code, the following problem occurs:

/usr/lib64/python3.6/asyncio/events.py:127: RuntimeWarning: coroutine 'foo' was never awaited self._callback(*self._args)

What is the correct way to handle this async call in the call_later?

like image 425
Nim Avatar asked Jan 13 '18 00:01

Nim


People also ask

Is coroutine deprecated?

"@coroutine" decorator is deprecated since Python 3.8, use "async def" instead. When importing thriftpy2. contrib. aio modules with Python 3.8 or newer, a large number of deprecation warnings is trigger.

Does Run_until_complete block?

run_until_complete is used to run a future until it's finished. It will block the execution of code following it. It does, however, cause the event loop to run.

Which function is used to run Awaitables concurrently in Asyncio?

gather() method - It runs awaitable objects (objects which have await keyword) concurrently.


1 Answers

call_later() only supports callbacks (regular functions); you simply can’t pass in a coroutine.

If you want to delay a coroutine, you have two options; either delay the coroutine by having it sleep at the start, or call asyncio.create_task() from call_later(), which does take a coroutine and schedules it to run.

use asyncio.sleep() at the start of the routine, you can have the loop execute it directly:

async def foo(iv):
    # delay start of the work
    await asyncio.sleep(2)

    # rest of your coroutine

You could easily use a wrapper coroutine to do this:

async def await_coro_later(delay, coro, *args, **kwargs):
    await asyncio.sleep(delay)
    await coro(*args, **kwargs)

If you use asyncio.create_task() (or, for Python 3.6 or older, asyncio.ensure_future(), you can pass that to call_later():

# create a task for foo(10) later
loop.call_later(2, asyncio.create_task, foo(10))

Demo of either technique:

>>> import asyncio
>>> async def foo(iv, start):
...     await asyncio.sleep(1)
...     offset = asyncio.get_running_loop().time() - start
...     print(f'done ({offset:.3f}s): {iv}')
...
>>> async def await_coro_later(delay, coro, *args, **kwargs):
...     await asyncio.sleep(delay)
...     await coro(*args, **kwargs)
...
>>> async def demo():
...     loop = asyncio.get_running_loop()
...     start = loop.time()
...     loop.call_later(2, asyncio.create_task, foo('cb_to_create_task', start))
...     await await_coro_later(5, foo, 'coro_later', start)
...
>>> asyncio.run(demo())
done (3.004s): cb_to_create_task
done (6.006s): coro_later
like image 181
Martijn Pieters Avatar answered Sep 28 '22 09:09

Martijn Pieters