Refs #31224 -- Doc'd async adapter functions.
This commit is contained in:
parent
8725d04764
commit
40a64dd1e2
|
@ -194,7 +194,7 @@ list of errors.
|
||||||
|
|
||||||
If you are trying to call code that is synchronous-only from an
|
If you are trying to call code that is synchronous-only from an
|
||||||
asynchronous thread, then create a synchronous thread and call it in that.
|
asynchronous thread, then create a synchronous thread and call it in that.
|
||||||
You can accomplish this is with ``asgiref.sync.sync_to_async``.
|
You can accomplish this is with :func:`asgiref.sync.sync_to_async`.
|
||||||
|
|
||||||
.. currentmodule:: django.urls
|
.. currentmodule:: django.urls
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ autogenerated
|
||||||
autoincrement
|
autoincrement
|
||||||
autoreload
|
autoreload
|
||||||
autovacuum
|
autovacuum
|
||||||
|
awaitable
|
||||||
Azerbaijani
|
Azerbaijani
|
||||||
backend
|
backend
|
||||||
backends
|
backends
|
||||||
|
@ -115,6 +116,7 @@ concat
|
||||||
conf
|
conf
|
||||||
config
|
config
|
||||||
contenttypes
|
contenttypes
|
||||||
|
contextvars
|
||||||
contrib
|
contrib
|
||||||
coroutine
|
coroutine
|
||||||
coroutines
|
coroutines
|
||||||
|
@ -667,6 +669,7 @@ th
|
||||||
that'll
|
that'll
|
||||||
Thejaswi
|
Thejaswi
|
||||||
This'll
|
This'll
|
||||||
|
threadlocals
|
||||||
threadpool
|
threadpool
|
||||||
timeframe
|
timeframe
|
||||||
timeline
|
timeline
|
||||||
|
|
|
@ -4,6 +4,8 @@ Asynchronous support
|
||||||
|
|
||||||
.. versionadded:: 3.0
|
.. versionadded:: 3.0
|
||||||
|
|
||||||
|
.. currentmodule:: asgiref.sync
|
||||||
|
|
||||||
Django has developing support for asynchronous ("async") Python, but does not
|
Django has developing support for asynchronous ("async") Python, but does not
|
||||||
yet support asynchronous views or middleware; they will be coming in a future
|
yet support asynchronous views or middleware; they will be coming in a future
|
||||||
release.
|
release.
|
||||||
|
@ -15,7 +17,7 @@ safety support.
|
||||||
.. _async-safety:
|
.. _async-safety:
|
||||||
|
|
||||||
Async-safety
|
Async-safety
|
||||||
------------
|
============
|
||||||
|
|
||||||
Certain key parts of Django are not able to operate safely in an asynchronous
|
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
|
environment, as they have global state that is not coroutine-aware. These parts
|
||||||
|
@ -28,13 +30,14 @@ event loop*, you will get a
|
||||||
:exc:`~django.core.exceptions.SynchronousOnlyOperation` error. Note that you
|
: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
|
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
|
you have called a synchronous function directly from an asynchronous function
|
||||||
without going through something like ``sync_to_async`` or a threadpool, then it
|
without going through something like :func:`sync_to_async` or a threadpool,
|
||||||
can also occur, as your code is still running in an asynchronous context.
|
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
|
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
|
code from an async context; instead, write your code that talks to async-unsafe
|
||||||
in its own, synchronous function, and call that using
|
in its own, synchronous function, and call that using
|
||||||
``asgiref.sync.async_to_sync``, or any other preferred way of running
|
:func:`asgiref.sync.async_to_sync`, or any other preferred way of running
|
||||||
synchronous code in its own thread.
|
synchronous code in its own thread.
|
||||||
|
|
||||||
If you are *absolutely* in dire need to run this code from an asynchronous
|
If you are *absolutely* in dire need to run this code from an asynchronous
|
||||||
|
@ -54,3 +57,103 @@ If you need to do this from within Python, do that with ``os.environ``::
|
||||||
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
||||||
|
|
||||||
.. _Jupyter: https://jupyter.org/
|
.. _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.
|
||||||
|
|
Loading…
Reference in New Issue