将协程传递给 AbstractEventLoop.call_later

Passing a coroutine to AbstractEventLoop.call_later

以下是我正在尝试运行的代码:

>>> 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)

基本上 foo() 函数有一些链式 async 调用,因此此方法必须是 async,因为链式调用需要 await。但是这个方法是在延迟之后触发的,当运行这段代码时,会出现以下问题:

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

call_later 中处理此 async 调用的正确方法是什么?

call_later() 仅支持回调(常规函数);你根本无法传入协程。

如果你想延迟协程,你有两个选择;要么通过让它在开始时休眠来延迟协程,或者从 call_later() 调用 asyncio.create_task() 确实 接受一个协程并将其安排到 运行 .

在例程开始时使用asyncio.sleep(),可以让循环直接执行:

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

    # rest of your coroutine

您可以轻松地使用包装协程来执行此操作:

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

如果您使用 asyncio.create_task()(或者,对于 Python 3.6 或更早版本,asyncio.ensure_future(),您可以将 that 传递给 call_later():

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

任一技术的演示:

>>> 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