123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- ====================
- 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>`. 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 async support for the ORM and other parts of Django.
- You can expect to see this in future releases. For now, you can use the
- :func:`sync_to_async` adapter to interact with the sync parts of Django.
- There is also a whole range of async-native Python libraries that you can
- integrate with.
- .. 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, async views will run in their own, one-off event loop.
- This means you can use async features, like parallel async HTTP requests,
- without any issues, but you will not get the benefits of an async stack.
- The main benefits are the ability to service hundreds of connections without
- using Python threads. This allows you to use slow streaming, long-polling, and
- other exciting response types.
- If you want to use these, 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 both ASGI and WSGI mode, you can still safely use asynchronous support to
- run code in parallel rather than serially. This is especially handy when
- dealing with external APIs or data stores.
- 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. For example::
- from asgiref.sync import sync_to_async
- results = sync_to_async(Blog.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`. For example::
- from asgiref.sync import sync_to_async
- @sync_to_async
- def get_blog(pk):
- return Blog.objects.select_related('author').get(pk=pk)
- If you accidentally try to call a 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 also true of middleware. Django will attempt to minimize the number of
- context-switches between sync and async. 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.
- However, if 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 async mode for the view. Django will also hold the sync thread
- open for middleware exception propagation. This may not be noticeable at first,
- but adding this penalty of one thread per request can remove any async
- performance advantage.
- You should do your own performance testing to see what effect ASGI versus WSGI
- has on your code. In some cases, there may be a performance increase even for
- a purely synchronous codebase under ASGI because the request-handling code is
- still all running asynchronously. In general you will only want to enable ASGI
- mode if you have asynchronous code in your project.
- .. _async-safety:
- Async safety
- ============
- Certain key parts of Django are not able to operate safely in an async
- 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 async 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 sync function directly from an async function,
- without using :func:`sync_to_async` or similar, then it can also occur. This is
- because your code is still running in a thread with an active event loop, even
- though it may not be declared as async code.
- 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
- functions in its own, sync function, and call that using
- :func:`asgiref.sync.sync_to_async` (or any other way of running sync code in
- its own thread).
- You may still be forced to run sync code from an async context. For example,
- if the requirement is forced on you by an external environment, such as in a
- Jupyter_ notebook. If you are sure there is no chance of the code being run
- concurrently, and you *absolutely* need to run this sync code from an async
- context, then you can disable the warning by setting the
- ``DJANGO_ALLOW_ASYNC_UNSAFE`` environment variable to any value.
- .. 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``::
- import os
- os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
- .. _Jupyter: https://jupyter.org/
- Async adapter functions
- =======================
- It is necessary to adapt the calling style when calling sync code from an async
- context, or vice-versa. For this there are two adapter functions, from the
- ``asgiref.sync`` module: :func:`async_to_sync` and :func:`sync_to_async`. They
- are used to transition between the 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)
- Takes an async function and returns a sync function that wraps it. Can be used
- as either a direct wrapper or a decorator::
- from asgiref.sync import async_to_sync
- async def get_data(...):
- ...
- sync_get_data = async_to_sync(get_data)
- @async_to_sync
- async def get_other_data(...):
- ...
- The async 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 single async invocation 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 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)
- Takes a sync function and returns an async function that wraps it. 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(...):
- ...
- Threadlocals and contextvars values are preserved across the boundary in both
- directions.
- Sync 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 sync function will run in a
- brand new thread which is then closed once the invocation completes.
- * ``thread_sensitive=True``: the sync function will run in the same thread as
- all other ``thread_sensitive`` functions. 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 similar, it will fall back to
- running thread-sensitive functions in a single, shared thread, but this will
- not be 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. Also 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 in views.
- Rather than introduce potential compatibility issues with this code, we instead
- opted to add this mode so that all existing Django sync code runs in the same
- thread and thus is fully compatible with async mode. Note that sync 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.
|