7

I am trying to create a simple monitoring system that periodically checks things and logs them. Here is a cutdown example of the logic I am attempting to use but I keep getting a RuntimeWarning: coroutine 'foo' was never awaited error.

How should I reschedule an async method from itself?

Code in test.py:

import asyncio
from datetime import datetime

async def collect_data():
    await asyncio.sleep(1)
    return {"some_data": 1,}

async def foo(loop):
    results = await collect_data()
    # Log the results
    print("{}: {}".format(datetime.now(), results))
    # schedule to run again in X seconds
    loop.call_later(5, foo, loop)

if __name__ == '__main__':

    loop = asyncio.get_event_loop()
    loop.create_task(foo(loop))
    loop.run_forever()
    loop.close()

Error:

pi@raspberrypi [0] $ python test.py 
2018-01-03 01:59:22.924871: {'some_data': 1}
/usr/lib/python3.5/asyncio/events.py:126: RuntimeWarning: coroutine 'foo' was never awaited
  self._callback(*self._args)
Tim Hughes
  • 3,144
  • 2
  • 24
  • 24
  • 1
    Possible duplicate of [How can I periodically execute a function with asyncio?](https://stackoverflow.com/questions/37512182/how-can-i-periodically-execute-a-function-with-asyncio) –  Jan 03 '18 at 02:24

1 Answers1

10

call_later accepts a plain sync callback (a function defined with def). A coroutine function (async def) should be awaited to be executed.


The cool thing about asyncio is that it imitates imperative plain synchronous code in many ways. How would you solve this task for a plain function? I guess just sleep some time and recursively call function again. Do the same (almost - we should use synchronous sleep) with asyncio also:

import asyncio
from datetime import datetime


async def collect_data():
    await asyncio.sleep(1)
    return {"some_data": 1,}


async def foo(loop):
    results = await collect_data()

    # Log the results
    print("{}: {}".format(datetime.now(), results))

    # Schedule to run again in X seconds
    await asyncio.sleep(5)
    return (await foo(loop))


if __name__ ==  '__main__':
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(foo(loop))
    finally:
        loop.run_until_complete(loop.shutdown_asyncgens())  # Python 3.6 only
        loop.close()

If you sometime would need to run foo in the background alongside with other coroutines you can create a task. There is also shown a way to cancel task execution.

Update:

As Andrew pointed out, a plain loop is even better:

async def foo(loop):
    while True:
        results = await collect_data()

        # Log the results
        print("{}: {}".format(datetime.now(), results))

        # Wait before next iteration:
        await asyncio.sleep(5)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
  • 3
    A loop instead of recursive call is even better. – Andrew Svetlov Jan 04 '18 at 10:46
  • @AndrewSvetlov why is that? I started with a while loop but can now see also recursion can work, which is better? In the main example why is it returned, can't it just be called with await again to recurse? – jmoz Dec 07 '20 at 06:44