diff options
| author | Jon Janzen <jon@jonjanzen.com> | 2020-11-07 13:19:20 +0300 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-03-07 08:39:25 +0100 |
| commit | e83a88566a71a2353cebc35992c110be0f8628af (patch) | |
| tree | 466c863fc3bfe6fc9946b5a3f7163c62e58ecbb9 /docs | |
| parent | 9a07999aef7958c9b5441e368cd90646d0edc5c9 (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.txt | 4 | ||||
| -rw-r--r-- | docs/topics/async.txt | 2 | ||||
| -rw-r--r-- | docs/topics/signals.txt | 65 |
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 ===================== |
