Background tasks

Creating Tasks#

It is often desirable and very convenient to make usage of tasks in async Python. Sanic provides a convenient method to add tasks to the currently running loop. It is somewhat similar to asyncio.create_task. For adding tasks before the 'App' loop is running, see next section.

async def notify_server_started_after_five_seconds():
    await asyncio.sleep(5)
    print('Server successfully started!')

app.add_task(notify_server_started_after_five_seconds())

Sanic will attempt to automatically inject the app, passing it as an argument to the task.

async def auto_inject(app):
    await asyncio.sleep(5)
    print(app.name)

app.add_task(auto_inject)

Or you can pass the app argument explicitly.

async def explicit_inject(app):
    await asyncio.sleep(5)
    print(app.name)

app.add_task(explicit_inject(app))

Adding tasks before app.run#

It is possible to add background tasks before the App is run ie. before app.run. To add a task before the App is run, it is recommended to not pass the coroutine object (ie. one created by calling the async callable), but instead just pass the callable and Sanic will create the coroutine object on each worker. Note: the tasks that are added such are run as before_server_start jobs and thus run on every worker (and not in the main process). This has certain consequences, please read this comment on this issue for further details.

To add work on the main process, consider adding work to @app.main_process_start. Note: the workers won't start until this work is completed.

Example to add a task before app.run

async def slow_work():
   ...

async def even_slower(num):
   ...

app = Sanic(...)
app.add_task(slow_work) # Note: we are passing the callable and not coroutine object ...
app.add_task(even_slower(10)) # ... or we can call the function and pass the coroutine.
app.run(...)

Named tasks#

When creating a task, you can ask Sanic to keep track of it for you by providing a name.

app.add_task(slow_work, name="slow_task")

You can now retrieve that task instance from anywhere in your application using get_task.

task = app.get_task("slow_task")

If that task needs to be cancelled, you can do that with cancel_task. Make sure that you await it.

await app.cancel_task("slow_task")

All registered tasks can be found in the app.tasks property. To prevent cancelled tasks from filling up, you may want to run app.purge_tasks that will clear out any completed or cancelled tasks.

app.purge_tasks()

This pattern can be particularly useful with websockets:

async def receiver(ws):
    while True:
        message = await ws.recv()
        if not message:
            break
        print(f"Received: {message}")

@app.websocket("/feed")
async def feed(request, ws):
    task_name = f"receiver:{request.id}"
    request.app.add_task(receiver(ws), name=task_name)
    try:
        while True:
            await request.app.event("my.custom.event")
            await ws.send("A message")
    finally:
        # When the websocket closes, let's cleanup the task
        await request.app.cancel_task(task_name)
        request.app.purge_tasks()

Added in v21.12