Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python asyncio recursion with call_later

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)
like image 696
Tim Hughes Avatar asked Jan 29 '23 22:01

Tim Hughes


1 Answers

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)
like image 147
Mikhail Gerasimov Avatar answered Feb 12 '23 11:02

Mikhail Gerasimov