==================== 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 ` 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 ` 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 ` 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 ` 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.