When writing an application, asynchronous code allows you to speed up an application that has to deal with a high number of tasks simultaneously. With the Django 3.1 release, Django now supports async views, so if you are running ASGI, writing async specific tasks is now possible!

In this tutorial, we'll build an example using the async view and compare it to a sync view using a mock HTTP request service.

Why should you use asynchronous views?

Async views are a great way to handle concurrency in an application and provide many advantages over their synchronous view counterpart. They are far more efficient when it comes to handling resources and blocking I/O tasks, and they are far easier to write than sync views as resource efficiency eliminates the number of tasks that have to run simultaneously. An example of this would be to use async/await to schedule multiple tasks in a single thread.

If a task is CPU bound, your web application will have to wait for a response when sending a request. This leads to a blocking I/O and may require more cores on the operating system processes the server is running on. An example of this would be submitting SQL queries to a database in order to fetch a result or loading an image from the disk. Until the database is done running the query or the image loading is complete, the browser running the web app will be blocked.

On the other hand, when using an asynchronous task, the web application running in the browser would not have to wait for the response to complete. Instead, it can perform other tasks and complete the async request when there is a response from the server.

Here are some examples of async specific tasks:

  • Chat services application
  • Gateway APIs
  • An api frontend for Django REST framework
  • Dashboard applications that show current active connections, and dealing with request updates in real-time

Prerequisites

In order to successfully complete this tutorial, you'll need to use the following versions of Python and Django:

Set up a project

To create a new project, start by creating a new directory. Then, navigate inside the directory to set up a virtualenv, and install Django to initialize a project in the current directory. Next, execute the following command from a terminal window:

mkdir async-views
cd async-views
python3.8 -m venv
source asyncviews_env/bin/activate
python3.8 -m pip install django==3.1 httpx

To create a new Django project and test the development server running, execute the following commands from a terminal window:

django-admin startproject asyncviews .
python3.8 manage.py migrate
python3.8 manage.py runserver

The development server should be running now at http://127.0.0.1:8000/, and you will see the new Django project sample page (see below).

Create a mock view

Let's create a synchronous view that returns a simple JSON response. It takes an optional parameter task_id, which is going to be used in a later section to identify the URL called from another view. The time.sleep() is used to emulate a real-time response with latency so that it takes 1 second to build.

Create a new file called views.py and add the following:

import time
from django.http import JsonResponse

def api(request):
  time.sleep(1)
  payload = {"message": "Hello from Crowdbotics!"}
  if "task_id" in request.GET:
    payload["task_id"] = request.GET["task_id"]
  return JsonResponse(payload)

Now, create an API endpoint so that this view is displayed in a browser window. Open urls.py and modify it as follows:

from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path("api/", views.api)
]

Here is the output you should get following this step:

What is ASGI?

ASGI stands for Asynchronous Server Gateway Interface. It is the asynchronous follow-up to Web Server Gateway Interface (WSGI), and ASGI provides a standard for creating asynchronous Python-based web apps. While Async Views do work under WSGI, running an async view in a WSGI application does not provide concurrency when calling a view from outside.

To resolve this, let's test an example that is concurrently callable from the outside. Begin by installing an ASGI server using uvicorn, which is a single-threaded server. Next, change the runserver command to run the Django application as an ASGI instead of WSGI.

python3.8 -m pip install uvicorn
uvicorn --reload asyncviews.asgi:application

try our app estimate calculator CTA image

Create an async view with HTTPX

To demonstrate an asynchronous view, let's create one in views.py. This can be achieved by importing the module asyncio. We are going to create a non-blocking HTTP request, and the HTTP response will be sent back before the first sleep call. On each second, the API view is going to sleep for one second—this is done using a for loop. For comparison, a sync view in the exact same scenario will take 5 seconds to complete. The HTTP request is going to be sent to a mock API, which serves as a simple HTTP request and response service.

An async view function in Django is detected by the annotation async def, which then runs the async view in a thread within its own event loop. This gives the benefit of being able to do and run tasks concurrently inside the async views. An example of this is to fetch results from other API endpoints and combine the result in a new response.

Next, you're going to want to modify the views.py as shown below:

import asyncio
from time import sleep
import httpx
from django.http import HttpResponse

async def http_call_async():
  for num in range(1,6):
    await asyncio.sleep(1)
    print(num)
  async with httpx.AsyncClient() as client:
    r = await client.get("https://httpbin.org")
    print(r)

async def async_view(request):
  loop = asyncio.get_event_loop()
  loop.create_task(http_call_async())
  return HttpResponse('Non-blocking HTTP request')

Then, update the file urls.py accordingly:

urlpatterns = [
    path('admin/', admin.site.urls),
    path("api/", views.async_view)
]

The HTTP response coming from the mock API service is sent before the first sleep call. On running the URL api/, you should get the following immediate response:

And in the terminal window, you will get the following result:

"GET /async/ HTTP/1.1" 200 OK
1
2
3
4
5
<Response [200 OK]>

Comparing the async view with a sync view

Now that everything is in place, let's compare the same HTTP response that we are getting from the mock HTTP API service using a sync view. To do this, modify the views.py file as shown below. In this sync view, let's mimic a blocking HTTP request and response with a delay of 5 seconds.

import asyncio
from time import sleep
import httpx
from django.http import HttpResponse

async def http_call_async():
    for num in range(1, 6):
        await asyncio.sleep(1)
        print(num)
    async with httpx.AsyncClient() as client:
        r = await client.get("https://httpbin.org/")
        print(r)

def http_call_sync():
    for num in range(1, 6):
        sleep(1)
        print(num)
    r = httpx.get("https://httpbin.org/")
    print(r)


async def async_view(request):
  loop = asyncio.get_event_loop()
  loop.create_task(http_call_async())
  return HttpResponse('Non-blocking HTTP request')

def sync_view(request):
    http_call_sync()
    return HttpResponse("Blocking HTTP request")

Next, add the new endpoint in the urls.py:

from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path("api/", views.async_view),
    path("sync/", views.sync_view)
]

In the browser window where the development server is running, send a request to the API endpoint sync/. After a delay of 5 seconds, you should get the following response:

The output in the terminal window confirms the delay:

1
2
3
4
5
<Response [200 OK]>
"GET /sync/ HTTP/1.1" 200 OK

Conclusion

In this tutorial, we discussed a single scenario for running an async view and comparing it with a sync view, but the main objective is to get you familiar with the differences between the two different types of views using the Django framework so that you can apply asynchronous features yourself.

Currently, there is an ongoing discussion in the official Django forum where you can find a more comprehensive breakdown on the progress of adding async views and adding support to run the server for async views that uses ASGI (as described in this post) instead of WSGI.

If you're looking to build a scalable backend or full-stack applications, Crowdbotics is here to help! At Crowdbotics, the Django framework is an integral part of the RAD stack. We use Django and React Native to build universal applications from a single codebase, and the Crowdbotics App Builder automatically scaffolds apps with prebuilt React Native screens with a data model and an API service created using Django.

If you're interested in building a custom mobile app completely from scratch, Crowdbotics provides expert PMs and developers to manage your app build. Get in touch with us today for a detailed quote and development timeline!