Fixed #36315 -- Used TaskGroup instead of asyncio.gather().

This commit is contained in:
Thomas Grainger 2025-04-10 09:43:32 +01:00 committed by Jacob Walls
parent 2501958b51
commit 2768747526
2 changed files with 37 additions and 11 deletions

View file

@ -22,6 +22,28 @@ NONE_ID = _make_id(None)
NO_RECEIVERS = object()
async def _gather(*coros):
if len(coros) == 0:
return []
if len(coros) == 1:
return [await coros[0]]
async def run(i, coro):
results[i] = await coro
try:
async with asyncio.TaskGroup() as tg:
results = [None] * len(coros)
for i, coro in enumerate(coros):
tg.create_task(run(i, coro))
return results
except BaseExceptionGroup as exception_group:
if len(exception_group.exceptions) == 1:
raise exception_group.exceptions[0]
raise
class Signal:
"""
Base class for all signals
@ -186,7 +208,7 @@ class Signal:
If any receivers are asynchronous, they are called after all the
synchronous receivers via a single call to async_to_sync(). They are
also executed concurrently with asyncio.gather().
also executed concurrently with asyncio.TaskGroup().
Arguments:
@ -211,7 +233,7 @@ class Signal:
if async_receivers:
async def asend():
async_responses = await asyncio.gather(
async_responses = await _gather(
*(
receiver(signal=self, sender=sender, **named)
for receiver in async_receivers
@ -235,7 +257,7 @@ class Signal:
sync_to_async() adaption before executing any asynchronous receivers.
If any receivers are asynchronous, they are grouped and executed
concurrently with asyncio.gather().
concurrently with asyncio.TaskGroup().
Arguments:
@ -268,9 +290,9 @@ class Signal:
async def sync_send():
return []
responses, async_responses = await asyncio.gather(
responses, async_responses = await _gather(
sync_send(),
asyncio.gather(
_gather(
*(
receiver(signal=self, sender=sender, **named)
for receiver in async_receivers
@ -294,7 +316,7 @@ class Signal:
If any receivers are asynchronous, they are called after all the
synchronous receivers via a single call to async_to_sync(). They are
also executed concurrently with asyncio.gather().
also executed concurrently with asyncio.TaskGroup().
Arguments:
@ -340,7 +362,7 @@ class Signal:
return response
async def asend():
async_responses = await asyncio.gather(
async_responses = await _gather(
*(
asend_and_wrap_exception(receiver)
for receiver in async_receivers
@ -359,7 +381,7 @@ class Signal:
sync_to_async() adaption before executing any asynchronous receivers.
If any receivers are asynchronous, they are grouped and executed
concurrently with asyncio.gather.
concurrently with asyncio.TaskGroup.
Arguments:
@ -414,9 +436,9 @@ class Signal:
return err
return response
responses, async_responses = await asyncio.gather(
responses, async_responses = await _gather(
sync_send(),
asyncio.gather(
_gather(
*(asend_and_wrap_exception(receiver) for receiver in async_receivers),
),
)

View file

@ -323,11 +323,15 @@ in order to reduce the number of sync/async calling-style switches within a
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()``.
:class:`asyncio.TaskGroup`.
All built-in signals, except those in the async request-response cycle, are
dispatched using :meth:`Signal.send`.
.. versionchanged:: 6.1
In older versions, async receivers were executed via ``asyncio.gather()``.
Disconnecting signals
=====================