pythonpython-asyncioquart

Scheduling periodic function call in Quart/asyncio


I need to schedule a periodic function call in python (ie. called every minute), without blocking the event loop (I'm using Quart framework with asyncio).

Essentially need to submit work onto the event loop, with a timer, so that the webserver keeps serving incoming requests in the meantime and roughly every minute it calls my function.

I tried many ways, for instance:

def do_work():
    print("WORK", flush=True)

async def schedule():
    await asyncio.sleep(0)
    print("scheduling")
    loop = asyncio.get_running_loop()
    t = loop.call_later(2, do_work)
    print("scheduled")
    

asyncio.run(schedule())

But it either never gets executed (like the code above), or it blocks the webserver main event loop. For instance, with the code above I would expect (since it's done within asyncio.run and schedule awaits timer) that "scheduling" would be printed after (or during) the server setup, but that's not the case, it blocks.


Solution

  • You can use a background task that is started on startup,

    async def schedule():
        while True:
            await asyncio.sleep(1)
            await do_work()
    
    @app.before_serving
    async def startup():
        app.add_background_task(schedule)
    

    which will run schedule for the lifetime of the app, being cancelled at shutdown.

    Edit 2024:

    Quart-Tasks now exists which allows the following,

    from quart import Quart
    from quart_tasks import QuartTasks
    
    app = Quart(__name__)
    tasks = QuartTasks(app)
    
    @tasks.periodic(timedelta(seconds=1))
    async def schedule():
        ...  # Do something
    

    It also allows the schedule to be defined using the CRON syntax.