Running background tasks in Django

For modern web applications, running asynchronous tasks in the background is, more often than not, a must. Whether you need to parallelize something not-so-time-critical (say, thumbnail generation) or access that miraculous-but-really-slow machine learning API in the background, there is a plethora of other use cases that require the developer to isolate time-consuming operations from Django’s default synchronous request-response cycle. This is done primarily in order to spare your users the experience of having to wait for a response while being left to stare at an unresponsive browser window.

Of course you could go ahead and develop your own asynchronous task queue by means of Python’s good ol’ threading module. But why reinvent the wheel? On the other hand, if asynchronous programming is an area where you think you could still learn something, then the DIY approach might be the way to go. In case you are going for a ready-made solution, keep on reading. Otherwise, well… Just leave this page, I guess.

Because executing code in the background is so important these days, various people have come up with various solutions to the problem. For Django alone, at least 3 well-supported packages are available via PyPI: Celery, Channels and Django Background Tasks.

Each of the mentioned packages is a great choice for implementing background tasks. In this post, I will focus on Django Background Tasks (DBT) as in my experience it’s the easiest to set up. This is mostly due to its simple design which is, by default, database-backed. Thanks to this property you are not required to install an external message broker such as Redis.

In order to set up DBT, you have to install the package by means of pip, add the background_task app to your INSTALLED_APPS and then migrate your database to install the required tables:

# In your shell
pip install django-background-tasks

# In settings.py
INSTALLED_APPS = (
    # ...
    'background_task',
    # ...
)

# In your shell again
python manage.py makemigrations background_task
python manage.py migrate

Next, it is your turn to decorate a function with that sweet @background decorator included with DBT. The convention is to define these background functions in one_of_your_apps/tasks.py but of course you could define them virtually anywhere in your codebase. Let’s stick with the convention, though, in order not to irritate your co-coders and risk exclusion from the next company event (“Why can’t I go to Hawaii like the rest of the crew?!”.

# In one_of_your_apps/tasks.py
from background_task import background

@background
def do_xyz_in_the_background(**kwargs):
    …

That’s it? Yep. Whenever you call this function, DBT will transparently create an instance of DBT’s Task model and append it to its database-backed task queue. If you needed to, you could still call the decorated function synchronously:

do_xyz_in_the_background.now(some_kwarg=123)

One more important thing: For the scheduled background tasks to actually be executed, you need to run the python manage.py process_tasks command in parallel with your Django server. process_tasks periodically checks the database for new Tasks and, if necessary, launches them asynchronously.