asyncio - await coroutine more than once (periodic tasks)

Arnaud H picture Arnaud H · Jun 30, 2018 · Viewed 7.6k times · Source

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?

Answer

Andrii Maletskyi picture Andrii Maletskyi · Jun 30, 2018

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)