Question: I am playing around with the new async views from Django 3.1. Some benefits I would love to have is to do some simple fire-and-forget "tasks" after the view already gave its HttpResponse, like sending a push notification or sending an email. I am not looking for solutions with third-party packages like celery! To test this async views I used some code from this tutorial: https://testdriven.io/blog/django-async-views/ async def http_call_async(): for num in range(1, 200): await asyncio.sleep(1) print(num) # TODO: send email async print('done') async def async_view(request): loop = asyncio.get_event_loop() loop.create_task(http_call_async()) return HttpResponse("Non-blocking HTTP request")  I started the django server with uvicorn. When I make a request to this view it will immediately return the HTTP-response "Non-blocking HTTP request". In the meantime and after the HTTP-response, the event loop continues happily to print the numbers up to 200 and then print "done". This is exactly the behaviour I want to utilize for my fire-and-forget tasks. Unfortunatly I could not find any information about the lifetime of the event loop running this code. How long does the event loop live? Is there a timeout? On what does it depend? On uvicorn? Is that configurable? Are there any resources which discuss this topic?

Answer: Django (which does not provide an ASGI server) does not limit the lifetime of the event loop. In the case of this question:  ASGI server: Uvicorn Event loop: either the built-in asyncio event loop or uvloop (both do not limit their lifetime)  For Uvicorn:  How long does the event loop live? The event loop lives as long as the worker lives.  Is there a timeout? No, tasks run until completion (unless the worker is unresponsive and is killed by Gunicorn).  On what does it depend? On Uvicorn? Worker lifetime is mainly limited by --limit-max-requests (Gunicorn: --max-requests).  Is that configurable? Yes, but the worker still exits at some point. It may also be killed for other reasons.    See the issue for yourself by specifying --limit-max-requests 2 and running the view twice: uvicorn mysite.asgi:application --limit-max-requests 2  Graceful shutdown Regardless of how the lifetime is limited, what we might care about is how to handle a shutdown. How does Uvicorn do graceful shutdown? Let's see how Uvicorn worker (uvicorn.server.Server) does graceful shutdown for requests.  uvicorn.protocols.http.h11_impl.H11Protocol#__init__: Store a reference to server tasks # Shared server state self.server_state = server_state self.tasks = server_state.tasks  uvicorn.protocols.http.h11_impl.H11Protocol#handle_events: Add request task to tasks self.cycle = RequestResponseCycle( ... ) task = self.loop.create_task(self.cycle.run_asgi(app)) task.add_done_callback(self.tasks.discard) self.tasks.add(task)  uvicorn.server.Server#shutdown: Wait for existing tasks to complete if self.server_state.tasks and not self.force_exit: msg = "Waiting for background tasks to complete. (CTRL+C to force quit)" logger.info(msg) while self.server_state.tasks and not self.force_exit: await asyncio.sleep(0.1)   How to make Uvicorn worker wait for your task? Let's piggyback on this by adding your tasks to the server tasks. async def async_view(request): loop = asyncio.get_event_loop() # loop.create_task(http_call_async()) # Replace this task = loop.create_task(http_call_async()) # with this server_state = await get_server_state() # Add this task.add_done_callback(server_state.tasks.discard) # Add this server_state.tasks.add(task) # Add this return HttpResponse("Non-blocking HTTP request")  import gc from uvicorn.server import ServerState _server_state = None @sync_to_async def get_server_state(): global _server_state if not _server_state: objects = gc.get_objects() _server_state = next(o for o in objects if isinstance(o, ServerState)) return _server_state  Now, Uvicorn worker will wait for your tasks in a graceful shutdown.