aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/async/eratosthenes/doc/eratosthenes.rst30
-rw-r--r--examples/async/eratosthenes/eratosthenes_asyncio.py89
-rw-r--r--examples/async/minimal/doc/minimal.rst30
-rw-r--r--examples/async/minimal/minimal_asyncio.py71
4 files changed, 30 insertions, 190 deletions
diff --git a/examples/async/eratosthenes/doc/eratosthenes.rst b/examples/async/eratosthenes/doc/eratosthenes.rst
index 494a94df3..5b8303911 100644
--- a/examples/async/eratosthenes/doc/eratosthenes.rst
+++ b/examples/async/eratosthenes/doc/eratosthenes.rst
@@ -6,22 +6,16 @@ The Python language provides keywords for asynchronous operations, i.e.,
event loop (see `PEP 492 <https://peps.python.org/pep-0492/>`_). It is up to
packages to implement an event loop, support for these keywords, and more.
-The best-known package for this is `asyncio`. Since both an async package and
-Qt itself work with event loops, special care must be taken to ensure that both
-event loops work with each other. asyncio offers a function `stop` that allows
-stopping an event loop without closing it. If it is called while a loop is
-running through `run_forever`, the loop will run the current batch of callbacks
-and then exit. New callbacks wil be scheduled the next time `run_forever` is
-called.
+The best-known package for this is `asyncio`. asyncio offers an API that allows
+for the asyncio event loop to be replaced by a custom implementation. Such an
+implementation is available with the `QtAsyncio` module. It is based on Qt and
+uses Qt's event loop in the backend.
-This approach is highly experimental and does not represent the state of the
-art of integrating Qt with asyncio. Instead it should rather be regarded more
-as a proof of concept to contrast asyncio with other async packages such as
-`trio`, which offers a dedicated `low-level API
+`trio` is another popular package that offers a dedicated `low-level API
<https://trio.readthedocs.io/en/stable/reference-lowlevel.html>`_ for more
-complicated use cases such as this. Specifically, there exists a function
-`start_guest_run` that enables running the Trio event loop as a "guest" inside
-another event loop - Qt's in our case.
+complex use cases. Specifically, there exists a function `start_guest_run` that
+enables running the Trio event loop as a "guest" inside another event loop -
+Qt's in our case, standing in contrast to asyncio's approach.
Based on this functionality, two examples for async usage with Qt have been
implemented: `eratosthenes` and `minimal`:
@@ -39,14 +33,6 @@ implemented: `eratosthenes` and `minimal`:
boilerplate code is essential for an async program with Qt and offers a
starting point for more complex programs.
-Both examples feature:
-
-1. A window class.
-2. An `AsyncHelper` class containing `start_guest_run` plus helpers and
- callbacks necessary for its invocation. The entry point for the Trio/asyncio
- guest run is provided as an argument from outside, which can be any async
- function.
-
While `eratosthenes` offloads the asynchronous logic that will run in
trio's/asyncio's event loop into a separate class, `minimal` demonstrates that
async functions can be integrated into any class, including subclasses of Qt
diff --git a/examples/async/eratosthenes/eratosthenes_asyncio.py b/examples/async/eratosthenes/eratosthenes_asyncio.py
index a5177a94b..c7b124587 100644
--- a/examples/async/eratosthenes/eratosthenes_asyncio.py
+++ b/examples/async/eratosthenes/eratosthenes_asyncio.py
@@ -1,10 +1,12 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from PySide6.QtCore import (Qt, QEvent, QObject, QTimer, Signal, Slot)
+from PySide6.QtCore import (Qt, QObject, Signal, Slot)
from PySide6.QtGui import (QColor, QFont, QPalette)
from PySide6.QtWidgets import (QApplication, QGridLayout, QLabel, QMainWindow, QVBoxLayout, QWidget)
+from PySide6.QtAsyncio import QAsyncioEventLoopPolicy
+
import asyncio
import signal
import sys
@@ -125,79 +127,6 @@ class Eratosthenes(QObject):
self.done_signal.emit()
-class AsyncHelper(QObject):
-
- class ReenterQtObject(QObject):
- """ This is a QObject to which an event will be posted, allowing
- asyncio to resume when the event is handled. event.fn() is
- the next entry point of the asyncio event loop. """
- def event(self, event):
- if event.type() == QEvent.Type.User + 1:
- event.fn()
- return True
- return False
-
- class ReenterQtEvent(QEvent):
- """ This is the QEvent that will be handled by the ReenterQtObject.
- self.fn is the next entry point of the asyncio event loop. """
- def __init__(self, fn):
- super().__init__(QEvent.Type(QEvent.Type.User + 1))
- self.fn = fn
-
- def __init__(self, worker, entry):
- super().__init__()
- self.reenter_qt = self.ReenterQtObject()
- self.entry = entry
- self.loop = asyncio.new_event_loop()
- self.done = False
-
- self.worker = worker
- if hasattr(self.worker, "start_signal") and isinstance(self.worker.start_signal, Signal):
- self.worker.start_signal.connect(self.on_worker_started)
- if hasattr(self.worker, "done_signal") and isinstance(self.worker.done_signal, Signal):
- self.worker.done_signal.connect(self.on_worker_done)
-
- @Slot()
- def on_worker_started(self):
- """ To use asyncio and Qt together, one must run the asyncio
- event loop as a "guest" inside the Qt "host" event loop. """
- if not self.entry:
- raise Exception("No entry point for the asyncio event loop was set.")
- asyncio.set_event_loop(self.loop)
- self.loop.create_task(self.entry())
- self.loop.call_soon(self.next_guest_run_schedule)
- self.done = False # Set this explicitly as we might want to restart the guest run.
- self.loop.run_forever()
-
- @Slot()
- def on_worker_done(self):
- """ When all our current asyncio tasks are finished, we must end
- the "guest run" lest we enter a quasi idle loop of switching
- back and forth between the asyncio and Qt loops. We can
- launch a new guest run by calling launch_guest_run() again. """
- self.done = True
-
- def continue_loop(self):
- """ This function is called by an event posted to the Qt event
- loop to continue the asyncio event loop. """
- if not self.done:
- self.loop.call_soon(self.next_guest_run_schedule)
- self.loop.run_forever()
-
- def next_guest_run_schedule(self):
- """ This function serves to pause and re-schedule the guest
- (asyncio) event loop inside the host (Qt) event loop. It is
- registered in asyncio as a callback to be called at the next
- iteration of the event loop. When this function runs, it
- first stops the asyncio event loop, then by posting an event
- on the Qt event loop, it both relinquishes to Qt's event
- loop and also schedules the asyncio event loop to run again.
- Upon handling this event, a function will be called that
- resumes the asyncio event loop. """
- self.loop.stop()
- QApplication.postEvent(self.reenter_qt, self.ReenterQtEvent(self.continue_loop))
-
-
if __name__ == "__main__":
rows = 40
cols = 40
@@ -206,15 +135,11 @@ if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow(rows, cols)
eratosthenes = Eratosthenes(num, main_window)
- async_helper = AsyncHelper(eratosthenes, eratosthenes.start)
-
- # This establishes the entry point for the asyncio guest run. It
- # varies depending on how and when its event loop is to be
- # triggered, e.g., from the beginning (as here) or rather at a
- # specific moment like a button press.
- QTimer.singleShot(0, async_helper.on_worker_started)
main_window.show()
signal.signal(signal.SIGINT, signal.SIG_DFL)
- app.exec()
+
+ asyncio.set_event_loop_policy(QAsyncioEventLoopPolicy())
+ asyncio.ensure_future(eratosthenes.start())
+ asyncio.get_event_loop().run_forever()
diff --git a/examples/async/minimal/doc/minimal.rst b/examples/async/minimal/doc/minimal.rst
index 5a1cf8544..10af70910 100644
--- a/examples/async/minimal/doc/minimal.rst
+++ b/examples/async/minimal/doc/minimal.rst
@@ -6,22 +6,16 @@ The Python language provides keywords for asynchronous operations, i.e.,
event loop (see `PEP 492 <https://peps.python.org/pep-0492/>`_). It is up to
packages to implement an event loop, support for these keywords, and more.
-The best-known package for this is `asyncio`. Since both an async package and
-Qt itself work with event loops, special care must be taken to ensure that both
-event loops work with each other. asyncio offers a function `stop` that allows
-stopping an event loop without closing it. If it is called while a loop is
-running through `run_forever`, the loop will run the current batch of callbacks
-and then exit. New callbacks wil be scheduled the next time `run_forever` is
-called.
+The best-known package for this is `asyncio`. asyncio offers an API that allows
+for the asyncio event loop to be replaced by a custom implementation. Such an
+implementation is available with the `QtAsyncio` module. It is based on Qt and
+uses Qt's event loop in the backend.
-This approach is highly experimental and does not represent the state of the
-art of integrating Qt with asyncio. Instead it should rather be regarded more
-as a proof of concept to contrast asyncio with other async packages such as
-`trio`, which offers a dedicated `low-level API
+`trio` is another popular package that offers a dedicated `low-level API
<https://trio.readthedocs.io/en/stable/reference-lowlevel.html>`_ for more
-complicated use cases such as this. Specifically, there exists a function
-`start_guest_run` that enables running the Trio event loop as a "guest" inside
-another event loop - Qt's in our case.
+complex use cases. Specifically, there exists a function `start_guest_run` that
+enables running the Trio event loop as a "guest" inside another event loop -
+Qt's in our case, standing in contrast to asyncio's approach.
Based on this functionality, two examples for async usage with Qt have been
implemented: `eratosthenes` and `minimal`:
@@ -38,14 +32,6 @@ implemented: `eratosthenes` and `minimal`:
boilerplate code is essential for an async program with Qt and offers a
starting point for more complex programs.
-Both examples feature:
-
-1. A window class.
-2. An `AsyncHelper` class containing `start_guest_run` plus helpers and
- callbacks necessary for its invocation. The entry point for the Trio/asyncio
- guest run is provided as an argument from outside, which can be any async
- function.
-
While `eratosthenes` offloads the asynchronous logic that will run in
trio's/asyncio's event loop into a separate class, `minimal` demonstrates that
async functions can be integrated into any class, including subclasses of Qt
diff --git a/examples/async/minimal/minimal_asyncio.py b/examples/async/minimal/minimal_asyncio.py
index 80c81da3b..a66e07ef6 100644
--- a/examples/async/minimal/minimal_asyncio.py
+++ b/examples/async/minimal/minimal_asyncio.py
@@ -1,9 +1,11 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from PySide6.QtCore import (Qt, QEvent, QObject, Signal, Slot)
+from PySide6.QtCore import (Qt, QObject, Signal, Slot)
from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget)
+from PySide6.QtAsyncio import QAsyncioEventLoopPolicy
+
import asyncio
import signal
import sys
@@ -12,7 +14,6 @@ import sys
class MainWindow(QMainWindow):
start_signal = Signal()
- done_signal = Signal()
def __init__(self):
super().__init__()
@@ -36,80 +37,20 @@ class MainWindow(QMainWindow):
async def set_text(self):
await asyncio.sleep(1)
self.text.setText("What do you get if you multiply six by nine?")
- self.done_signal.emit()
class AsyncHelper(QObject):
- class ReenterQtObject(QObject):
- """ This is a QObject to which an event will be posted, allowing
- asyncio to resume when the event is handled. event.fn() is
- the next entry point of the asyncio event loop. """
- def event(self, event):
- if event.type() == QEvent.Type.User + 1:
- event.fn()
- return True
- return False
-
- class ReenterQtEvent(QEvent):
- """ This is the QEvent that will be handled by the ReenterQtObject.
- self.fn is the next entry point of the asyncio event loop. """
- def __init__(self, fn):
- super().__init__(QEvent.Type(QEvent.Type.User + 1))
- self.fn = fn
-
def __init__(self, worker, entry):
super().__init__()
- self.reenter_qt = self.ReenterQtObject()
self.entry = entry
- self.loop = asyncio.new_event_loop()
- self.done = False
-
self.worker = worker
if hasattr(self.worker, "start_signal") and isinstance(self.worker.start_signal, Signal):
self.worker.start_signal.connect(self.on_worker_started)
- if hasattr(self.worker, "done_signal") and isinstance(self.worker.done_signal, Signal):
- self.worker.done_signal.connect(self.on_worker_done)
@Slot()
def on_worker_started(self):
- """ To use asyncio and Qt together, one must run the asyncio
- event loop as a "guest" inside the Qt "host" event loop. """
- if not self.entry:
- raise Exception("No entry point for the asyncio event loop was set.")
- asyncio.set_event_loop(self.loop)
- self.loop.create_task(self.entry())
- self.loop.call_soon(self.next_guest_run_schedule)
- self.done = False # Set this explicitly as we might want to restart the guest run.
- self.loop.run_forever()
-
- @Slot()
- def on_worker_done(self):
- """ When all our current asyncio tasks are finished, we must end
- the "guest run" lest we enter a quasi idle loop of switching
- back and forth between the asyncio and Qt loops. We can
- launch a new guest run by calling launch_guest_run() again. """
- self.done = True
-
- def continue_loop(self):
- """ This function is called by an event posted to the Qt event
- loop to continue the asyncio event loop. """
- if not self.done:
- self.loop.call_soon(self.next_guest_run_schedule)
- self.loop.run_forever()
-
- def next_guest_run_schedule(self):
- """ This function serves to pause and re-schedule the guest
- (asyncio) event loop inside the host (Qt) event loop. It is
- registered in asyncio as a callback to be called at the next
- iteration of the event loop. When this function runs, it
- first stops the asyncio event loop, then by posting an event
- on the Qt event loop, it both relinquishes to Qt's event
- loop and also schedules the asyncio event loop to run again.
- Upon handling this event, a function will be called that
- resumes the asyncio event loop. """
- self.loop.stop()
- QApplication.postEvent(self.reenter_qt, self.ReenterQtEvent(self.continue_loop))
+ asyncio.ensure_future(self.entry())
if __name__ == "__main__":
@@ -120,4 +61,6 @@ if __name__ == "__main__":
main_window.show()
signal.signal(signal.SIGINT, signal.SIG_DFL)
- app.exec()
+
+ asyncio.set_event_loop_policy(QAsyncioEventLoopPolicy())
+ asyncio.get_event_loop().run_forever()