2

From version 3.1 Django supports async views. I have a Django app running on uvicorn. I'm trying to write an async view, which can handle multiple requests to itself concurrently, but have no success.

Common examples, I've seen, include making multiple slow I/O operations from inside the view:

async def slow_io(n, result): await asyncio.sleep(n) return result async def my_view(request, *args, **kwargs): task_1 = asyncio.create_task(slow_io(3, 'Hello')) task_2 = asyncio.create_task(slow_io(5, 'World')) result = await task_1 + await task_2 return HttpResponse(result) 

This will yield us "HelloWorld" after 5 seconds instead of 8 because requests are run concurrently.

What I want - is to concurrently handle multiple requests TO my_view. E.g. I expect this code to handle 2 simultaneous requests in 5 seconds, but it takes 10.

async def slow_io(n, result): await asyncio.sleep(n) return result async def my_view(request, *args, **kwargs): result = await slow_io(5, 'result') return HttpResponse(result) 

I run uvicorn with this command:

uvicorn --host 0.0.0.0 --port 8000 main.asgi:application --reload 

Django doc says:

The main benefits are the ability to service hundreds of connections without using Python threads.

So it's possible.

What am I missing?

UPD: It seems, my testing setup was wrong. I was opening multiple tabs in browser and refreshing them all at once. See my answer for details.

2
  • 1
    How do you send your requests? Commented Dec 21, 2021 at 19:24
  • @lucutzu33 I open multiple tabs in my browser and refresh them all at once. It's not precise, but large timeout (5, 10 seconds) is enough to see that delay between tabs loading is much longer than it takes to refresh them. Also I added timestamp logging and it shows exact 5 seconds between requests. Commented Dec 21, 2021 at 21:22

2 Answers 2

2

Here is a sample project on Django 3.2 with multiple async views and tests. I tested it multiple ways:

  • Requests from Django's test client are handled simultaneously, as expected.
  • Requests to different views from single client are handled simultaneously, as expected.
  • Requests to the same view from different clients are handled simultaneously, as expected.

What doesn't work as expected?

  • Requests to the same view from single client are handled by one at a time, and I didn't expect that.

There is a warning in Django doc:

You will only get the benefits of a fully-asynchronous request stack if you have no synchronous middleware loaded into your site. If there is a piece of synchronous middleware, then Django must use a thread per request to safely emulate a synchronous environment for it.

Middleware can be built to support both sync and async contexts. Some of Django’s middleware is built like this, but not all. To see what middleware Django has to adapt, you can turn on debug logging for the django.request logger and look for log messages about “Synchronous middleware … adapted”.

So it might be some sync middleware causing the problem even in the bare Django. But it also states that Django should use threads in this case and I didn't use any sync middleware, only standard ones.

My best guess, that it's related to client-server connection and not to sync or async stack. Despite Google says that browsers create new connections for each tab due to security reasons, I think:

  • Browser might keep the same connection for multiple tabs if the url is the same for economy reasons.
  • Django creates async tasks or threads per connection and not per request, as it states.

Need to check it out.

Sign up to request clarification or add additional context in comments.

1 Comment

"The view project.views.aa didn't return an HttpResponse object. It returned an unawaited coroutine instead. You may need to add an 'await' into your view." like this, I am getting error
0

Your problem is that you write code like sync version. And you await every function result and only after this, you await next function.

You simple need use asyncio functions like gather to run all tasks asynchronously:

import asyncio async def slow_io(n, result): await asyncio.sleep(n) return result async def my_view(request, *args, **kwargs): all_tasks_result = await asyncio.gather(slow_io(3, 'Hello '), slow_io(5, "World")) result = "".join(all_tasks_result) return HttpResponse(result) 

2 Comments

Please, check my answer. You describe the first case from the question, and it works as expected. But I am talking about the second case - a single async task in a view, which I have to wait, to put result in a response. And multiple simultatneous requests to that view, which I want to be handled simultaneously. It seems not to be related to sync or async stack, but how I've tested it and how browsers and Django work.
"The view project.views.aa didn't return an HttpResponse object. It returned an unawaited coroutine instead. You may need to add an 'await' into your view." like this, I am getting error.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.