summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorJon Janzen <jon@jonjanzen.com>2020-11-07 13:19:20 +0300
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-03-07 08:39:25 +0100
commite83a88566a71a2353cebc35992c110be0f8628af (patch)
tree466c863fc3bfe6fc9946b5a3f7163c62e58ecbb9 /docs
parent9a07999aef7958c9b5441e368cd90646d0edc5c9 (diff)
Fixed #32172 -- Adapted signals to allow async handlers.
co-authored-by: kozzztik <kozzztik@mail.ru> co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es>
Diffstat (limited to 'docs')
-rw-r--r--docs/releases/5.0.txt4
-rw-r--r--docs/topics/async.txt2
-rw-r--r--docs/topics/signals.txt65
3 files changed, 62 insertions, 9 deletions
diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt
index c7546338ef..83cb6284a5 100644
--- a/docs/releases/5.0.txt
+++ b/docs/releases/5.0.txt
@@ -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
~~~~~~~~~
diff --git a/docs/topics/async.txt b/docs/topics/async.txt
index 2541ab8e05..96220b97c1 100644
--- a/docs/topics/async.txt
+++ b/docs/topics/async.txt
@@ -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
-----------
diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt
index 601634c309..a4d973ebb4 100644
--- a/docs/topics/signals.txt
+++ b/docs/topics/signals.txt
@@ -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
=====================