mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	This implements support for asynchronous views, asynchronous tests, asynchronous middleware, and an asynchronous test client.
		
			
				
	
	
		
			253 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			253 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| ====================
 | |
| Asynchronous support
 | |
| ====================
 | |
| 
 | |
| .. versionadded:: 3.0
 | |
| 
 | |
| .. currentmodule:: asgiref.sync
 | |
| 
 | |
| Django has support for writing asynchronous ("async") views, along with an
 | |
| entirely async-enabled request stack if you are running under
 | |
| :doc:`ASGI </howto/deployment/asgi/index>` rather than WSGI. Async views will
 | |
| still work under WSGI, but with performance penalties, and without the ability
 | |
| to have efficient long-running requests.
 | |
| 
 | |
| We're still working on asynchronous support for the ORM and other parts of
 | |
| Django; you can expect to see these in future releases. For now, you can use
 | |
| the :func:`sync_to_async` adapter to interact with normal Django, as well as
 | |
| use a whole range of Python asyncio libraries natively. See below for more
 | |
| details.
 | |
| 
 | |
| .. versionchanged:: 3.1
 | |
| 
 | |
|     Support for async views was added.
 | |
| 
 | |
| Async views
 | |
| ===========
 | |
| 
 | |
| .. versionadded:: 3.1
 | |
| 
 | |
| Any view can be declared async by making the callable part of it return a
 | |
| coroutine - commonly, this is done using ``async def``. For a function-based
 | |
| view, this means declaring the whole view using ``async def``. For a
 | |
| class-based view, this means making its ``__call__()`` method an ``async def``
 | |
| (not its ``__init__()`` or ``as_view()``).
 | |
| 
 | |
| .. note::
 | |
| 
 | |
|     Django uses ``asyncio.iscoroutinefunction`` to test if your view is
 | |
|     asynchronous or not. If you implement your own method of returning a
 | |
|     coroutine, ensure you set the ``_is_coroutine`` attribute of the view
 | |
|     to ``asyncio.coroutines._is_coroutine`` so this function returns ``True``.
 | |
| 
 | |
| Under a WSGI server, asynchronous views will run in their own, one-off event
 | |
| loop. This means that you can do things like parallel, async HTTP calls to APIs
 | |
| without any issues, but you will not get the benefits of an asynchronous
 | |
| request stack.
 | |
| 
 | |
| If you want these benefits - which are mostly around the ability to service
 | |
| hundreds of connections without using any Python threads (enabling slow
 | |
| streaming, long-polling, and other exciting response types) - you will need to
 | |
| deploy Django using :doc:`ASGI </howto/deployment/asgi/index>` instead.
 | |
| 
 | |
| .. warning::
 | |
| 
 | |
|     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 :ref:`both sync and async
 | |
|     <async-middleware>` 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"*.
 | |
| 
 | |
| In either ASGI or WSGI mode, though, you can safely use asynchronous support to
 | |
| run code in parallel rather than serially, which is especially handy when
 | |
| dealing with external APIs or datastores.
 | |
| 
 | |
| If you want to call a part of Django that is still synchronous (like the ORM)
 | |
| you will need to wrap it in a :func:`sync_to_async` call, like this::
 | |
| 
 | |
|     from asgiref.sync import sync_to_async
 | |
| 
 | |
|     results = sync_to_async(MyModel.objects.get)(pk=123)
 | |
| 
 | |
| You may find it easier to move any ORM code into its own function and call that
 | |
| entire function using :func:`sync_to_async`. If you accidentally try to call
 | |
| part of Django that is still synchronous-only from an async view, you will
 | |
| trigger Django's :ref:`asynchronous safety protection <async-safety>` to
 | |
| protect your data from corruption.
 | |
| 
 | |
| Performance
 | |
| -----------
 | |
| 
 | |
| When running in a mode that does not match the view (e.g. an async view under
 | |
| WSGI, or a traditional sync view under ASGI), Django must emulate the other
 | |
| call style to allow your code to run. This context-switch causes a small
 | |
| performance penalty of around a millisecond.
 | |
| 
 | |
| This is true of middleware as well, however. Django will attempt to minimize
 | |
| the number of context-switches. If you have an ASGI server, but all your
 | |
| middleware and views are synchronous, it will switch just once, before it
 | |
| enters the middleware stack.
 | |
| 
 | |
| If, however, you put synchronous middleware between an ASGI server and an
 | |
| asynchronous view, it will have to switch into sync mode for the middleware and
 | |
| then back to asynchronous mode for the view, holding the synchronous thread
 | |
| open for middleware exception propagation. This may not be noticeable, but bear
 | |
| in mind that even adding a single piece of synchronous middleware can drag your
 | |
| whole async project down to running with one thread per request, and the
 | |
| associated performance penalties.
 | |
| 
 | |
| You should do your own performance testing to see what effect ASGI vs. WSGI has
 | |
| on your code. In some cases, there may be a performance increase even for
 | |
| purely-synchronous codebase under ASGI because the request-handling code is
 | |
| still all running asynchronously. In general, though, you will only want to
 | |
| enable ASGI mode if you have asynchronous code in your site.
 | |
| 
 | |
| .. _async-safety:
 | |
| 
 | |
| Async-safety
 | |
| ============
 | |
| 
 | |
| Certain key parts of Django are not able to operate safely in an asynchronous
 | |
| environment, as they have global state that is not coroutine-aware. These parts
 | |
| of Django are classified as "async-unsafe", and are protected from execution in
 | |
| an asynchronous environment. The ORM is the main example, but there are other
 | |
| parts that are also protected in this way.
 | |
| 
 | |
| If you try to run any of these parts from a thread where there is a *running
 | |
| event loop*, you will get a
 | |
| :exc:`~django.core.exceptions.SynchronousOnlyOperation` error. Note that you
 | |
| don't have to be inside an async function directly to have this error occur. If
 | |
| you have called a synchronous function directly from an asynchronous function
 | |
| without going through something like :func:`sync_to_async` or a threadpool,
 | |
| then it can also occur, as your code is still running in an asynchronous
 | |
| context.
 | |
| 
 | |
| If you encounter this error, you should fix your code to not call the offending
 | |
| code from an async context; instead, write your code that talks to async-unsafe
 | |
| in its own, synchronous function, and call that using
 | |
| :func:`asgiref.sync.sync_to_async`, or any other preferred way of running
 | |
| synchronous code in its own thread.
 | |
| 
 | |
| If you are *absolutely* in dire need to run this code from an asynchronous
 | |
| context - for example, it is being forced on you by an external environment,
 | |
| and you are sure there is no chance of it being run concurrently (e.g. you are
 | |
| in a Jupyter_ notebook), then you can disable the warning with the
 | |
| ``DJANGO_ALLOW_ASYNC_UNSAFE`` environment variable.
 | |
| 
 | |
| .. warning::
 | |
| 
 | |
|     If you enable this option and there is concurrent access to the
 | |
|     async-unsafe parts of Django, you may suffer data loss or corruption. Be
 | |
|     very careful and do not use this in production environments.
 | |
| 
 | |
| If you need to do this from within Python, do that with ``os.environ``::
 | |
| 
 | |
|     os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
 | |
| 
 | |
| .. _Jupyter: https://jupyter.org/
 | |
| 
 | |
| Async adapter functions
 | |
| =======================
 | |
| 
 | |
| It is necessary to adapt the calling style when calling synchronous code from
 | |
| an asynchronous context, or vice-versa. For this there are two adapter
 | |
| functions, made available from the ``asgiref.sync`` package:
 | |
| :func:`async_to_sync` and :func:`sync_to_async`. They are used to transition
 | |
| between sync and async calling styles while preserving compatibility.
 | |
| 
 | |
| These adapter functions are widely used in Django. The `asgiref`_ package
 | |
| itself is part of the Django project, and it is automatically installed as a
 | |
| dependency when you install Django with ``pip``.
 | |
| 
 | |
| .. _asgiref: https://pypi.org/project/asgiref/
 | |
| 
 | |
| ``async_to_sync()``
 | |
| -------------------
 | |
| 
 | |
| .. function:: async_to_sync(async_function, force_new_loop=False)
 | |
| 
 | |
| Wraps an asynchronous function and returns a synchronous function in its place.
 | |
| Can be used as either a direct wrapper or a decorator::
 | |
| 
 | |
|     from asgiref.sync import async_to_sync
 | |
| 
 | |
|     sync_function = async_to_sync(async_function)
 | |
| 
 | |
|     @async_to_sync
 | |
|     async def async_function(...):
 | |
|         ...
 | |
| 
 | |
| The asynchronous function is run in the event loop for the current thread, if
 | |
| one is present. If there is no current event loop, a new event loop is spun up
 | |
| specifically for the async function and shut down again once it completes. In
 | |
| either situation, the async function will execute on a different thread to the
 | |
| calling code.
 | |
| 
 | |
| Threadlocals and contextvars values are preserved across the boundary in both
 | |
| directions.
 | |
| 
 | |
| :func:`async_to_sync` is essentially a more powerful version of the
 | |
| :py:func:`asyncio.run` function available in Python's standard library. As well
 | |
| as ensuring threadlocals work, it also enables the ``thread_sensitive`` mode of
 | |
| :func:`sync_to_async` when that wrapper is used below it.
 | |
| 
 | |
| ``sync_to_async()``
 | |
| -------------------
 | |
| 
 | |
| .. function:: sync_to_async(sync_function, thread_sensitive=False)
 | |
| 
 | |
| Wraps a synchronous function and returns an asynchronous (awaitable) function
 | |
| in its place. Can be used as either a direct wrapper or a decorator::
 | |
| 
 | |
|     from asgiref.sync import sync_to_async
 | |
| 
 | |
|     async_function = sync_to_async(sync_function)
 | |
|     async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)
 | |
| 
 | |
|     @sync_to_async
 | |
|     def sync_function(...):
 | |
|         ...
 | |
| 
 | |
|     @sync_to_async(thread_sensitive=True)
 | |
|     def sensitive_sync_function(...):
 | |
|         ...
 | |
| 
 | |
| Threadlocals and contextvars values are preserved across the boundary in both
 | |
| directions.
 | |
| 
 | |
| Synchronous functions tend to be written assuming they all run in the main
 | |
| thread, so :func:`sync_to_async` has two threading modes:
 | |
| 
 | |
| * ``thread_sensitive=False`` (the default): the synchronous function will run
 | |
|   in a brand new thread which is then closed once it completes.
 | |
| 
 | |
| * ``thread_sensitive=True``: the synchronous function will run in the same
 | |
|   thread as all other ``thread_sensitive`` functions, and this will be the main
 | |
|   thread, if the main thread is synchronous and you are using the
 | |
|   :func:`async_to_sync` wrapper.
 | |
| 
 | |
| Thread-sensitive mode is quite special, and does a lot of work to run all
 | |
| functions in the same thread. Note, though, that it *relies on usage of*
 | |
| :func:`async_to_sync` *above it in the stack* to correctly run things on the
 | |
| main thread. If you use ``asyncio.run()`` (or other options instead), it will
 | |
| fall back to just running thread-sensitive functions in a single, shared thread
 | |
| (but not the main thread).
 | |
| 
 | |
| The reason this is needed in Django is that many libraries, specifically
 | |
| database adapters, require that they are accessed in the same thread that they
 | |
| were created in, and a lot of existing Django code assumes it all runs in the
 | |
| same thread (e.g. middleware adding things to a request for later use by a
 | |
| view).
 | |
| 
 | |
| Rather than introduce potential compatibility issues with this code, we instead
 | |
| opted to add this mode so that all existing Django synchronous code runs in the
 | |
| same thread and thus is fully compatible with asynchronous mode. Note, that
 | |
| synchronous code will always be in a *different* thread to any async code that
 | |
| is calling it, so you should avoid passing raw database handles or other
 | |
| thread-sensitive references around in any new code you write.
 |