4
votes

I am trying to create a periodic task for an asyncio event loop as shown below, however I am getting a "RuntimeError: cannot reuse already awaited coroutine" exception. Apparently, asyncio does not allow for the same awaitable function to be awaited as discussed in this bug thread. This is how I tried to implement it:

import asyncio    

class AsyncEventLoop:    

    def __init__(self):
        self._loop = asyncio.get_event_loop()

    def add_periodic_task(self, async_func, interval):
        async def wrapper(_async_func, _interval):
            while True:
                await _async_func               # This is where it goes wrong
                await asyncio.sleep(_interval)
        self._loop.create_task(wrapper(async_func, interval))
        return

    def start(self):
        self._loop.run_forever()
        return

Because of my while loop, the same awaitable function (_async_func) would be executed with a sleep interval in between. I got my inspiration for the implementation of periodic tasks from How can I periodically execute a function with asyncio? .

From the bug thread mentioned above, I infer that the idea behind the RuntimeError was so that developers wouldn't accidentally await the same coroutine twice or more, as the coroutine would be marked as done and yield None instead of the result. Is there a way I can await the same function more than once?

1
To exacerbate the confusion, the description of the linked bug has a horrible typo, making it look like it proposes to disallow instantiating and awaiting the same coroutine function twice in a row! The [subsequent comments] (bugs.python.org/msg256567) clear up the confusion, making it clear that awaiting an already awaited coroutine object is disallowed.user4815162342
With that out of the way, consider what would happen if the old behavior were restored and the RuntimeError removed. The periodic coroutine would not be magically reset and started from the beginning (there is no mechanism in Python to implement that automatically, nor is one needed, since the same thing can be achieved using a loop). Instead, the next await would produce a bogus None value, which is not what the anyone would expect or want.user4815162342

1 Answers

6
votes

It seems you are confusing async functions (coroutine functions) with coroutines - values that these async functions produce.

Consider this async function:

async def sample():
    await asyncio.sleep(3.14)

You are passing result of its call: add_periodic_task(sample(), 5).

Instead, you should pass async function object itself: add_periodic_task(sample, 5), and call it within your wrapper:

while True:
    await _async_func()
    await asyncio.sleep(_interval)