asyncio的call_later引发'generator'对象不能用coroutine对象

时间:2015-11-30 17:02:04

标签: python python-asyncio

我使用call_later使用Python 3.4的asyncio制作了一些简单的代码。代码应该打印,等待10秒,然后再次打印(但是当TypeError应该被激活时引发end(),见下文):

import asyncio

@asyncio.coroutine
def begin():
    print("Starting to wait.")
    asyncio.get_event_loop().call_later(10, end())

@asyncio.coroutine
def end():
    print("completed")

if __name__ == "__main__":
    try:
        loop = asyncio.get_event_loop()
        loop.create_task(begin())
        loop.run_forever()
    except KeyboardInterrupt:
        print("Goodbye!")

给出错误:

Exception in callback <generator object coro at 0x7fc88eeaddc8>()
handle: <TimerHandle when=31677.188005054 <generator object coro at 0x7fc88eeaddc8>()>
Traceback (most recent call last):
  File "/usr/lib64/python3.4/asyncio/events.py", line 119, in _run
    self._callback(*self._args)
TypeError: 'generator' object is not callable

从我从文档(https://docs.python.org/3/library/asyncio-task.html#coroutine)中可以看出,call_later采用了一个协程对象,它是通过调用协程函数获得的。这似乎是我所做的,但asyncio没有正确调用end()

这应该怎么做?

1 个答案:

答案 0 :(得分:9)

call_later旨在进行回调(意味着常规函数对象),而非协程。较新版本的Python实际上会明确地说明这一点:

Starting to wait.
Task exception was never retrieved
future: <Task finished coro=<coro() done, defined at /usr/lib/python3.4/asyncio/coroutines.py:139> exception=TypeError('coroutines cannot be used with call_at()',)>
Traceback (most recent call last):
  File "/usr/lib/python3.4/asyncio/tasks.py", line 238, in _step
    result = next(coro)
  File "/usr/lib/python3.4/asyncio/coroutines.py", line 141, in coro
    res = func(*args, **kw)
  File "aio.py", line 6, in begin
    asyncio.get_event_loop().call_later(10, end())
  File "/usr/lib/python3.4/asyncio/base_events.py", line 392, in call_later
    timer = self.call_at(self.time() + delay, callback, *args)
  File "/usr/lib/python3.4/asyncio/base_events.py", line 404, in call_at
    raise TypeError("coroutines cannot be used with call_at()")
TypeError: coroutines cannot be used with call_at()

为了使您的代码有效,end需要是常规函数,然后将其传递给call_later

import asyncio

@asyncio.coroutine
def begin():
    print("Starting to wait.")
    asyncio.get_event_loop().call_later(10, end)

def end():
    print("completed")

if __name__ == "__main__":
    try:
        loop = asyncio.get_event_loop()
        loop.create_task(begin())
        loop.run_forever()
    except KeyboardInterrupt:
        print("Goodbye!")

输出:

Starting to wait.
completed
Goodbye!

如果end需要成为协程,那么在延迟之后调用它的更自然的方法是使用asyncio.sleep

import asyncio

@asyncio.coroutine
def begin():
    print("Starting to wait.")
    yield from asyncio.sleep(10)
    yield from end()

@asyncio.coroutine
def end():
    print("completed")

if __name__ == "__main__":
    try:
        loop = asyncio.get_event_loop()
        loop.create_task(begin())
        loop.run_forever()
    except KeyboardInterrupt:
        print("Goodbye!")

虽然从技术上讲,这确实有效:

asyncio.get_event_loop().call_later(10, lambda: asyncio.async(end()))