Clarified async documentation.
This commit is contained in:
parent
6ef4c8aa9f
commit
4216225480
|
@ -144,7 +144,6 @@ databrowse
|
|||
datafile
|
||||
dataset
|
||||
datasets
|
||||
datastores
|
||||
datatype
|
||||
datetimes
|
||||
Debian
|
||||
|
|
|
@ -8,15 +8,15 @@ Asynchronous support
|
|||
|
||||
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.
|
||||
: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 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.
|
||||
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
|
||||
|
||||
|
@ -40,20 +40,21 @@ class-based view, this means making its ``__call__()`` method an ``async def``
|
|||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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
|
||||
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.
|
||||
|
||||
|
@ -63,22 +64,30 @@ deploy Django using :doc:`ASGI </howto/deployment/asgi/index>` instead.
|
|||
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
|
||||
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, like this::
|
||||
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(MyModel.objects.get)(pk=123)
|
||||
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`. 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.
|
||||
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
|
||||
-----------
|
||||
|
@ -88,56 +97,56 @@ 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
|
||||
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.
|
||||
|
||||
If, however, you put synchronous middleware between an ASGI server and an
|
||||
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 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.
|
||||
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 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.
|
||||
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
|
||||
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 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 asynchronous environment. The ORM is the main example, but there are other
|
||||
parts that are also protected in this way.
|
||||
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 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.
|
||||
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
|
||||
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.
|
||||
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).
|
||||
|
||||
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.
|
||||
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::
|
||||
|
||||
|
@ -147,6 +156,8 @@ in a Jupyter_ notebook), then you can disable the warning with the
|
|||
|
||||
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/
|
||||
|
@ -154,11 +165,11 @@ If you need to do this from within Python, do that with ``os.environ``::
|
|||
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.
|
||||
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
|
||||
|
@ -171,28 +182,31 @@ dependency when you install Django with ``pip``.
|
|||
|
||||
.. 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::
|
||||
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
|
||||
|
||||
sync_function = async_to_sync(async_function)
|
||||
|
||||
@async_to_sync
|
||||
async def async_function(...):
|
||||
async def get_data(...):
|
||||
...
|
||||
|
||||
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.
|
||||
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 available in Python's standard library. As well
|
||||
: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.
|
||||
|
||||
|
@ -201,8 +215,8 @@ as ensuring threadlocals work, it also enables the ``thread_sensitive`` mode of
|
|||
|
||||
.. 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::
|
||||
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
|
||||
|
||||
|
@ -220,33 +234,32 @@ in its place. Can be used as either a direct wrapper or a decorator::
|
|||
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
|
||||
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 synchronous function will run
|
||||
in a brand new thread which is then closed once it completes.
|
||||
* ``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 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=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 other options instead), it will
|
||||
fall back to just running thread-sensitive functions in a single, shared thread
|
||||
(but not the main thread).
|
||||
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, 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).
|
||||
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 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.
|
||||
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.
|
||||
|
|
|
@ -308,10 +308,10 @@ on your middleware factory function or class:
|
|||
asynchronous requests. Defaults to ``False``.
|
||||
|
||||
If your middleware has both ``sync_capable = True`` and
|
||||
``async_capable = True``, then Django will pass it the request in whatever form
|
||||
it is currently in. You can work out what type of request you have by seeing
|
||||
if the ``get_response`` object you are passed is a coroutine function or not
|
||||
(using :py:func:`asyncio.iscoroutinefunction`).
|
||||
``async_capable = True``, then Django will pass it the request without
|
||||
converting it. In this case, you can work out if your middleware will receive
|
||||
async requests by checking if the ``get_response`` object you are passed is a
|
||||
coroutine function, using :py:func:`asyncio.iscoroutinefunction`.
|
||||
|
||||
The ``django.utils.decorators`` module contains
|
||||
:func:`~django.utils.decorators.sync_only_middleware`,
|
||||
|
@ -328,8 +328,7 @@ methods, if they are provided, should also be adapted to match the sync/async
|
|||
mode. However, Django will individually adapt them as required if you do not,
|
||||
at an additional performance penalty.
|
||||
|
||||
Here's an example of how to detect and adapt your middleware if it supports
|
||||
both::
|
||||
Here's an example of how to create a middleware function that supports both::
|
||||
|
||||
import asyncio
|
||||
from django.utils.decorators import sync_and_async_middleware
|
||||
|
|
|
@ -205,25 +205,26 @@ in a test view. For example::
|
|||
|
||||
.. _async-views:
|
||||
|
||||
Asynchronous views
|
||||
==================
|
||||
Async views
|
||||
===========
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
As well as being synchronous functions, views can also be asynchronous
|
||||
functions (``async def``). Django will automatically detect these and run them
|
||||
in an asynchronous context. You will need to be using an asynchronous (ASGI)
|
||||
server to get the full power of them, however.
|
||||
("async") functions, normally defined using Python's ``async def`` syntax.
|
||||
Django will automatically detect these and run them in an async context.
|
||||
However, you will need to use an async server based on ASGI to get their
|
||||
performance benefits.
|
||||
|
||||
Here's an example of an asynchronous view::
|
||||
Here's an example of an async view::
|
||||
|
||||
from django.http import HttpResponse
|
||||
import datetime
|
||||
from django.http import HttpResponse
|
||||
|
||||
async def current_datetime(request):
|
||||
now = datetime.datetime.now()
|
||||
html = '<html><body>It is now %s.</body></html>' % now
|
||||
return HttpResponse(html)
|
||||
|
||||
You can read more about Django's asynchronous support, and how to best use
|
||||
asynchronous views, in :doc:`/topics/async`.
|
||||
You can read more about Django's async support, and how to best use async
|
||||
views, in :doc:`/topics/async`.
|
||||
|
|
|
@ -1808,7 +1808,7 @@ creates.
|
|||
|
||||
@mock.patch(...)
|
||||
@async_to_sync
|
||||
def test_my_thing(self):
|
||||
async def test_my_thing(self):
|
||||
...
|
||||
|
||||
.. _topics-testing-email:
|
||||
|
|
Loading…
Reference in New Issue