10

I have some simple code code made with Python 3.4's asyncio using call_later. The code should print, waits 10 seconds, and then print again (but instead raises TypeError when end() should be excecuted, see below):

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

Gives the error:

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 

From what I can tell from the docs (https://docs.python.org/3/library/asyncio-task.html#coroutine), call_later takes a coroutine object, which is obtained by calling a coroutine function. This appears to be what I've done, but asyncio does not call end() properly.

How is this supposed to be done?

1 Answer 1

10

call_later is designed to take a callback (meaning a regular function object), not a coroutine. Newer versions of Python will actually say this explicitly:

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

To make your code work, end needs to be a regular function, which you then pass to 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!") 

Output:

Starting to wait. completed Goodbye! 

If end needs to be a coroutine, a more natural way to call it after a delay would be to use 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!") 

Though technically, this does work:

asyncio.get_event_loop().call_later(10, lambda: asyncio.async(end())) 
Sign up to request clarification or add additional context in comments.

2 Comments

In that case, is there a way to schedule a coroutine to be called later with asyncio? Or is there some reason why this doesn't make sense to do?
@NathanaelFarley Well, you can use call_later(10, lambda: asyncio.ensure_future(end())). But it probably makes more sense to just put a yield from asyncio.sleep(10) inside begin, and then call yield from end() right after that. If you don't want to block begin, you could just put the asyncio.sleep and call to end in another coroutine, and just call asyncio.ensure_future(other_coroutine()) inside begin instead.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.