Django Asyc function execution

162 views Asked by At

I am trying to execute the functions in async mode using Django DRF APIView, so that the API returns the response instantly. I am using Django 4.1.12, adrf for async View. Below is the code :

import asyncio
from asgiref.sync import sync_to_async
from adrf.views import APIView


def sensitive_sync_function():
    count = 0
    while True:
        count = count + 1
        print("Running in Async mode !")
        time.sleep(1)
        if count == 10:
            print("Processing done !")
            break
    return None


class AsyncGroupManagementView(APIView):
    async def get(self, request, format=None):
        async_function = sync_to_async(sensitive_sync_function)
        return Response(response, status=status.HTTP_200_OK, headers=headers)

The Api call executes successfully and returns response, but I am not sure if the sensitive_sync_function() executed or not as I am not able to see any logs on terminal. Do I have to explicitly execute this task later ?

My end goal is to run the function in async mode, so the API response is returned and the function keeps on executing in background. Is this the correct approach or should I use celery here ?

Any AWS cloud based solution for this scenario are also welcomed !

2

There are 2 answers

7
Igonato On BEST ANSWER

Celery is pretty much the tool for the job, but if you don't want to go through the troubles of setting it up, you might get away using asyncio.create_task

import asyncio
from asgiref.sync import sync_to_async
from adrf.views import APIView

@sync_to_async
def sensitive_sync_function():
    count = 0
    while True:
        count = count + 1
        print("Running in Async mode !")
        time.sleep(1)
        if count == 10:
            print("Processing done !")
            break
    return None


class AsyncGroupManagementView(APIView):
    async def get(self, request, format=None):
        asyncio.create_task(sensitive_sync_function())  # note the ()
        return Response(response, status=status.HTTP_200_OK, headers=headers)

Notice that once sensitive_sync_function is running it will still block the thread. You should convert it to async and use asyncio.sleep instead of time.sleep.

Finally, it is important that you run your app using a proper ASGI server.

To use ASGI during development, install Daphne server, which comes with its own runserver command:

$ pip install daphne

Edit your settings.py:

INSTALLED_APPS = [
    "daphne",  # <---
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    ...
]

...

ASGI_APPLICATION = "yourproject.asgi.application"  # <---

Now, python manage.py runserver should run the app properly.

4
xyres On

sync_to_async function is basically a decorator. When you give it a function, it will return a wrapped async function.

But you're still required to execute the returned async_function yourself.

Now, since you mention that you want to return the response without waiting for the async_function to finish running. That means you can't use the await statement to execute it.

So, you'll have to use event loop's create_task() function to run the async_function later:

async def get(self, request, format=None):
    async_function = sync_to_async(sensitive_sync_function)

    loop = asyncio.get_event_loop()
    # now ask the even loop to schedule your coroutine's execution
    loop.create_task(async_function())
    
    return Response(...)

Some tips:

  • To execute a coroutine "right now" and wait for the result, use the await statement.
  • To execute a coroutine a later time without waiting for it to finish, use event loop's create_task() function.