【问题标题】:asyncio's call_later raises 'generator' object is not callable with coroutine objectasyncio 的 call_later 引发“生成器”对象不能用协程对象调用
【发布时间】:2016-03-04 09:42:28
【问题描述】:

我有一些使用 Python 3.4 的 asyncio 使用 call_later 编写的简单代码。代码应该打印,等待 10 秒,然后再次打印(而是在应该执行 end() 时引发 TypeError,见下文):

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

这应该怎么做?

【问题讨论】:

    标签: python python-asyncio


    【解决方案1】:

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

    【讨论】:

    • 在这种情况下,有没有办法安排一个协程稍后用asyncio 调用?还是有什么理由说明这样做没有意义?
    • @NathanaelFarley 好吧,你可以使用call_later(10, lambda: asyncio.ensure_future(end()))。但是将yield from asyncio.sleep(10) 放在begin 中可能更有意义,然后立即调用yield from end()。如果你不想阻塞begin,你可以把asyncio.sleep和调用end放在另一个协程中,然后在begin里面调用asyncio.ensure_future(other_coroutine())
    猜你喜欢
    • 1970-01-01
    • 2017-06-04
    • 2020-10-18
    • 2017-03-06
    • 2012-08-17
    • 2019-12-06
    • 2021-11-11
    • 2021-08-18
    • 2023-03-09
    相关资源
    最近更新 更多