mirror of
https://github.com/django/django.git
synced 2025-10-24 14:16:09 +00:00
Fixed #32172 -- Adapted signals to allow async handlers.
co-authored-by: kozzztik <kozzztik@mail.ru> co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es>
This commit is contained in:
committed by
Mariusz Felisiak
parent
9a07999aef
commit
e83a88566a
@@ -218,7 +218,9 @@ Serialization
|
||||
Signals
|
||||
~~~~~~~
|
||||
|
||||
* ...
|
||||
* The new :meth:`.Signal.asend` and :meth:`.Signal.asend_robust` methods allow
|
||||
asynchronous signal dispatch. Signal receivers may be synchronous or
|
||||
asynchronous, and will be automatically adapted to the correct calling style.
|
||||
|
||||
Templates
|
||||
~~~~~~~~~
|
||||
|
||||
@@ -108,6 +108,8 @@ synchronous function and call it using :func:`sync_to_async`.
|
||||
|
||||
Asynchronous model and related manager interfaces were added.
|
||||
|
||||
.. _async_performance:
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
|
||||
@@ -96,6 +96,21 @@ This would be wrong -- in fact, Django will throw an error if you do so. That's
|
||||
because at any point arguments could get added to the signal and your receiver
|
||||
must be able to handle those new arguments.
|
||||
|
||||
Receivers may also be asynchronous functions, with the same signature but
|
||||
declared using ``async def``::
|
||||
|
||||
async def my_callback(sender, **kwargs):
|
||||
await asyncio.sleep(5)
|
||||
print("Request finished!")
|
||||
|
||||
Signals can be sent either synchronously or asynchronously, and receivers will
|
||||
automatically be adapted to the correct call-style. See :ref:`sending signals
|
||||
<sending-signals>` for more information.
|
||||
|
||||
.. versionchanged:: 5.0
|
||||
|
||||
Support for asynchronous receivers was added.
|
||||
|
||||
.. _connecting-receiver-functions:
|
||||
|
||||
Connecting receiver functions
|
||||
@@ -248,18 +263,26 @@ For example::
|
||||
|
||||
This declares a ``pizza_done`` signal.
|
||||
|
||||
.. _sending-signals:
|
||||
|
||||
Sending signals
|
||||
---------------
|
||||
|
||||
There are two ways to send signals in Django.
|
||||
There are two ways to send signals synchronously in Django.
|
||||
|
||||
.. method:: Signal.send(sender, **kwargs)
|
||||
.. method:: Signal.send_robust(sender, **kwargs)
|
||||
|
||||
To send a signal, call either :meth:`Signal.send` (all built-in signals use
|
||||
this) or :meth:`Signal.send_robust`. You must provide the ``sender`` argument
|
||||
(which is a class most of the time) and may provide as many other keyword
|
||||
arguments as you like.
|
||||
Signals may also be sent asynchronously.
|
||||
|
||||
.. method:: Signal.asend(sender, **kwargs)
|
||||
.. method:: Signal.asend_robust(sender, **kwargs)
|
||||
|
||||
To send a signal, call either :meth:`Signal.send`, :meth:`Signal.send_robust`,
|
||||
:meth:`await Signal.asend()<Signal.asend>`, or
|
||||
:meth:`await Signal.asend_robust() <Signal.asend_robust>`. You must provide the
|
||||
``sender`` argument (which is a class most of the time) and may provide as many
|
||||
other keyword arguments as you like.
|
||||
|
||||
For example, here's how sending our ``pizza_done`` signal might look::
|
||||
|
||||
@@ -270,9 +293,8 @@ For example, here's how sending our ``pizza_done`` signal might look::
|
||||
pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
|
||||
...
|
||||
|
||||
Both ``send()`` and ``send_robust()`` return a list of tuple pairs
|
||||
``[(receiver, response), ... ]``, representing the list of called receiver
|
||||
functions and their response values.
|
||||
All four methods return a list of tuple pairs ``[(receiver, response), ...]``,
|
||||
representing the list of called receiver functions and their response values.
|
||||
|
||||
``send()`` differs from ``send_robust()`` in how exceptions raised by receiver
|
||||
functions are handled. ``send()`` does *not* catch any exceptions raised by
|
||||
@@ -286,6 +308,33 @@ error instance is returned in the tuple pair for the receiver that raised the er
|
||||
The tracebacks are present on the ``__traceback__`` attribute of the errors
|
||||
returned when calling ``send_robust()``.
|
||||
|
||||
``asend()`` is similar as ``send()``, but it is coroutine that must be
|
||||
awaited::
|
||||
|
||||
async def asend_pizza(self, toppings, size):
|
||||
await pizza_done.asend(sender=self.__class__, toppings=toppings, size=size)
|
||||
...
|
||||
|
||||
Whether synchronous or asynchronous, receivers will be correctly adapted to
|
||||
whether ``send()`` or ``asend()`` is used. Synchronous receivers will be
|
||||
called using :func:`~.sync_to_async` when invoked via ``asend()``. Asynchronous
|
||||
receivers will be called using :func:`~.async_to_sync` when invoked via
|
||||
``sync()``. Similar to the :ref:`case for middleware <async_performance>`,
|
||||
there is a small performance cost to adapting receivers in this way. Note that
|
||||
in order to reduce the number of sync/async calling-style switches within a
|
||||
``send()`` or ``asend()`` call, the receivers are grouped by whether or not
|
||||
they are async before being called. This means that an asynchronous receiver
|
||||
registered before a synchronous receiver may be executed after the synchronous
|
||||
receiver. In addition, async receivers are executed concurrently using
|
||||
``asyncio.gather()``.
|
||||
|
||||
All built-in signals, except those in the async request-response cycle, are
|
||||
dispatched using :meth:`Signal.send`.
|
||||
|
||||
.. versionchanged:: 5.0
|
||||
|
||||
Support for asynchronous signals was added.
|
||||
|
||||
Disconnecting signals
|
||||
=====================
|
||||
|
||||
|
||||
Reference in New Issue
Block a user