aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6')
-rw-r--r--sources/pyside6/PySide6/QtAsyncio/__init__.py4
-rw-r--r--sources/pyside6/PySide6/QtAsyncio/futures.py6
-rw-r--r--sources/pyside6/PySide6/QtAsyncio/tasks.py41
3 files changed, 43 insertions, 8 deletions
diff --git a/sources/pyside6/PySide6/QtAsyncio/__init__.py b/sources/pyside6/PySide6/QtAsyncio/__init__.py
index 4baa8134e..60d1846d1 100644
--- a/sources/pyside6/PySide6/QtAsyncio/__init__.py
+++ b/sources/pyside6/PySide6/QtAsyncio/__init__.py
@@ -28,8 +28,8 @@ def run(coro: typing.Optional[typing.Coroutine] = None,
# subsequent removal in Python 3.15. At that point, part of the current
# logic of the QAsyncioEventLoopPolicy constructor will have to be moved
# here and/or to a loop factory class (to be provided as an argument to
- # asyncio.run()), namely setting up the QCoreApplication and the SIGINT
- # handler.
+ # asyncio.run()). In particular, this concerns the logic of setting up the
+ # QCoreApplication and the SIGINT handler.
#
# More details:
# https://discuss.python.org/t/removing-the-asyncio-policy-system-asyncio-set-event-loop-policy-in-python-3-15/37553 # noqa: E501
diff --git a/sources/pyside6/PySide6/QtAsyncio/futures.py b/sources/pyside6/PySide6/QtAsyncio/futures.py
index 611bd5634..cbb005fc9 100644
--- a/sources/pyside6/PySide6/QtAsyncio/futures.py
+++ b/sources/pyside6/PySide6/QtAsyncio/futures.py
@@ -36,10 +36,11 @@ class QAsyncioFuture():
self._result: typing.Any = None
self._exception: typing.Optional[BaseException] = None
- self._callbacks: typing.List[typing.Callable] = list()
-
self._cancel_message: typing.Optional[str] = None
+ # List of callbacks that are called when the future is done.
+ self._callbacks: typing.List[typing.Callable] = list()
+
def __await__(self):
if not self.done():
self._asyncio_future_blocking = True
@@ -51,6 +52,7 @@ class QAsyncioFuture():
__iter__ = __await__
def _schedule_callbacks(self, context: typing.Optional[contextvars.Context] = None):
+ """ A future can optionally have callbacks that are called when the future is done. """
for cb in self._callbacks:
self._loop.call_soon(
cb, self, context=context if context else self._context)
diff --git a/sources/pyside6/PySide6/QtAsyncio/tasks.py b/sources/pyside6/PySide6/QtAsyncio/tasks.py
index c04006686..7edc15093 100644
--- a/sources/pyside6/PySide6/QtAsyncio/tasks.py
+++ b/sources/pyside6/PySide6/QtAsyncio/tasks.py
@@ -20,17 +20,22 @@ class QAsyncioTask(futures.QAsyncioFuture):
context: typing.Optional[contextvars.Context] = None) -> None:
super().__init__(loop=loop, context=context)
- self._coro = coro
+ self._coro = coro # The coroutine for which this task was created.
self._name = name if name else "QtTask"
+ # The task creates a handle for its coroutine. The handle enqueues the
+ # task's step function as its callback in the event loop.
self._handle = self._loop.call_soon(self._step, context=self._context)
- self._cancellation_requests = 0
-
+ # The task step function executes the coroutine until it finishes,
+ # raises an exception or returns a future. If a future was returned,
+ # the task will await its completion (or exception).
self._future_to_await: typing.Optional[asyncio.Future] = None
- self._cancel_message: typing.Optional[str] = None
+
self._cancelled = False
+ self._cancel_message: typing.Optional[str] = None
+ # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support
asyncio._register_task(self) # type: ignore[arg-type]
def __repr__(self) -> str:
@@ -59,6 +64,14 @@ class QAsyncioTask(futures.QAsyncioFuture):
def _step(self,
exception_or_future: typing.Union[
BaseException, futures.QAsyncioFuture, None] = None) -> None:
+ """
+ The step function is the heart of a task. It is scheduled in the event
+ loop repeatedly, executing the coroutine "step" by "step" (i.e.,
+ iterating through the asynchronous generator) until it finishes with an
+ exception or successfully. Each step can optionally receive an
+ exception or a future as a result from a previous step to handle.
+ """
+
if self.done():
return
result = None
@@ -72,7 +85,15 @@ class QAsyncioTask(futures.QAsyncioFuture):
try:
asyncio._enter_task(self._loop, self) # type: ignore[arg-type]
+
+ # It is at this point that the coroutine is resumed for the current
+ # step (i.e. asynchronous generator iteration). It will now be
+ # executed until it yields (and potentially returns a future),
+ # raises an exception, is cancelled, or finishes successfully.
+
if isinstance(exception_or_future, BaseException):
+ # If the coroutine doesn't handle this exception, it propagates
+ # to the caller.
result = self._coro.throw(exception_or_future)
else:
result = self._coro.send(None)
@@ -87,6 +108,9 @@ class QAsyncioTask(futures.QAsyncioFuture):
self._exception = e
else:
if asyncio.futures.isfuture(result):
+ # If the coroutine yields a future, the task will await its
+ # completion, and at that point the step function will be
+ # called again.
result.add_done_callback(
self._step, context=self._context) # type: ignore[arg-type]
self._future_to_await = result
@@ -100,12 +124,16 @@ class QAsyncioTask(futures.QAsyncioFuture):
# very quickly.
self._future_to_await.cancel(self._cancel_message)
elif result is None:
+ # If no future was yielded, we schedule the step function again
+ # without any arguments.
self._loop.call_soon(self._step, context=self._context)
else:
+ # This is not supposed to happen.
exception = RuntimeError(f"Bad task result: {result}")
self._loop.call_soon(self._step, exception, context=self._context)
finally:
asyncio._leave_task(self._loop, self) # type: ignore[arg-type]
+
if self._exception:
self._loop.call_exception_handler({
"message": (str(self._exception) if self._exception
@@ -117,8 +145,11 @@ class QAsyncioTask(futures.QAsyncioFuture):
if asyncio.futures.isfuture(exception_or_future)
else None)
})
+
if self.done():
self._schedule_callbacks()
+
+ # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support
asyncio._unregister_task(self) # type: ignore[arg-type]
def get_stack(self, *, limit=None) -> typing.List[typing.Any]:
@@ -144,6 +175,8 @@ class QAsyncioTask(futures.QAsyncioFuture):
self._cancel_message = msg
self._handle.cancel()
if self._future_to_await is not None:
+ # A task that is awaiting a future must also cancel this future in
+ # order for the cancellation to be successful.
self._future_to_await.cancel(msg)
self._cancelled = True
return True