123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- ====================
- Asynchronous support
- ====================
- .. versionadded:: 3.0
- .. currentmodule:: asgiref.sync
- Django has developing support for asynchronous ("async") Python, but does not
- yet support asynchronous views or middleware; they will be coming in a future
- release.
- There is limited support for other parts of the async ecosystem; namely, Django
- can natively talk :doc:`ASGI </howto/deployment/asgi/index>`, and some async
- safety support.
- .. _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.async_to_sync`, 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.
|