aboutsummaryrefslogtreecommitdiffstats
path: root/examples/async
diff options
context:
space:
mode:
Diffstat (limited to 'examples/async')
-rw-r--r--examples/async/eratosthenes/doc/eratosthenes.rst33
-rw-r--r--examples/async/eratosthenes/eratosthenes.pyproject2
-rw-r--r--examples/async/eratosthenes/eratosthenes_asyncio.py134
-rw-r--r--examples/async/eratosthenes/eratosthenes_trio.py (renamed from examples/async/eratosthenes/eratosthenes.py)18
-rw-r--r--examples/async/eratosthenes/requirements_trio.txt (renamed from examples/async/eratosthenes/requirements.txt)0
-rw-r--r--examples/async/minimal/doc/minimal.rst33
-rw-r--r--examples/async/minimal/minimal.pyproject2
-rw-r--r--examples/async/minimal/minimal_asyncio.py40
-rw-r--r--examples/async/minimal/minimal_trio.py (renamed from examples/async/minimal/minimal.py)32
-rw-r--r--examples/async/minimal/requirements_trio.txt (renamed from examples/async/minimal/requirements.txt)0
10 files changed, 227 insertions, 67 deletions
diff --git a/examples/async/eratosthenes/doc/eratosthenes.rst b/examples/async/eratosthenes/doc/eratosthenes.rst
index fdb095a91..56e9f391b 100644
--- a/examples/async/eratosthenes/doc/eratosthenes.rst
+++ b/examples/async/eratosthenes/doc/eratosthenes.rst
@@ -1,18 +1,21 @@
-Async examples
-==============
+Async "Eratosthenes" Example
+============================
The Python language provides keywords for asynchronous operations, i.e.,
"async" to define coroutines or "await" to schedule asynchronous calls in the
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.
-One such package is `trio`. 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. trio offers a dedicated `low-level API
+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.
+
+`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`:
@@ -30,13 +33,7 @@ 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 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
-event loop into a separate class, `minimal` demonstrates that async functions
-can be integrated into any class, including subclasses of Qt classes.
+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
+classes.
diff --git a/examples/async/eratosthenes/eratosthenes.pyproject b/examples/async/eratosthenes/eratosthenes.pyproject
index 8ea189b3c..f0de627ef 100644
--- a/examples/async/eratosthenes/eratosthenes.pyproject
+++ b/examples/async/eratosthenes/eratosthenes.pyproject
@@ -1,3 +1,3 @@
{
- "files": ["eratosthenes.py"]
+ "files": ["eratosthenes_trio.py", "eratosthenes_asyncio.py"]
}
diff --git a/examples/async/eratosthenes/eratosthenes_asyncio.py b/examples/async/eratosthenes/eratosthenes_asyncio.py
new file mode 100644
index 000000000..c5d7fc2fe
--- /dev/null
+++ b/examples/async/eratosthenes/eratosthenes_asyncio.py
@@ -0,0 +1,134 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+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)
+
+import PySide6.QtAsyncio as QtAsyncio
+
+import asyncio
+import sys
+from random import randint
+
+
+class MainWindow(QMainWindow):
+
+ set_num = Signal(int, QColor)
+
+ def __init__(self, rows, cols):
+ super().__init__()
+
+ self.rows = rows
+ self.cols = cols
+
+ widget_central = QWidget()
+ self.setCentralWidget(widget_central)
+
+ layout_outer = QVBoxLayout(widget_central)
+
+ self.widget_outer_text = QLabel()
+ font = QFont()
+ font.setPointSize(14)
+ self.widget_outer_text.setFont(font)
+ layout_outer.addWidget(self.widget_outer_text, alignment=Qt.AlignmentFlag.AlignCenter)
+
+ widget_inner_grid = QWidget()
+ layout_outer.addWidget(widget_inner_grid, alignment=Qt.AlignmentFlag.AlignCenter)
+
+ self.layout_inner_grid = QGridLayout(widget_inner_grid)
+ k = 1
+ for i in range(self.rows):
+ for j in range(self.cols):
+ box = QLabel(f"{k}")
+ self.layout_inner_grid.addWidget(box, i, j, Qt.AlignmentFlag.AlignCenter)
+ k += 1
+
+ self.set_num.connect(self.set_num_handler)
+
+ @Slot(int, QColor)
+ def set_num_handler(self, i, color):
+ row = int((i - 1) / self.cols)
+ col = (i - 1) - (row * self.cols)
+ widget = self.layout_inner_grid.itemAtPosition(row, col).widget()
+
+ font = QFont()
+ font.setWeight(QFont.Bold)
+ palette = QPalette()
+ palette.setColor(QPalette.WindowText, color)
+ widget.setFont(font)
+ widget.setPalette(palette)
+
+
+class Eratosthenes(QObject):
+
+ """ This Sieve of Eratosthenes runs on a configurable tick (default
+ 0.1 seconds). At each tick, a new subroutine will be created
+ that will check multiples of the next prime number. Each of
+ these subroutines also operates on the same tick. """
+
+ def __init__(self, num, window, tick=0.1):
+ super().__init__()
+ self.num = num
+ self.sieve = [True] * self.num
+ self.base = 0
+ self.window = window
+ self.tick = tick
+ self.coroutines = []
+ self.done = False
+ self.loop = None
+
+ def get_tick(self):
+ return self.loop.time() + self.tick
+
+ async def start(self):
+ self.loop = asyncio.get_event_loop()
+ asyncio.create_task(self.update_text())
+ while self.base <= self.num / 2:
+ await asyncio.sleep(self.tick)
+ for i in range(self.base + 1, self.num):
+ if self.sieve[i]:
+ self.base = i
+ break
+ asyncio.create_task(self.mark_number(self.base + 1))
+ while sum(self.coroutines) > 0:
+ await asyncio.sleep(self.tick)
+ self.done = True
+
+ async def mark_number(self, base):
+ id = len(self.coroutines)
+ self.coroutines.append(1)
+ color = QColor(randint(64, 192), randint(64, 192), randint(64, 192))
+ for i in range(2 * base, self.num + 1, base):
+ if self.sieve[i - 1]:
+ self.sieve[i - 1] = False
+ self.window.set_num.emit(i, color)
+ await asyncio.sleep(self.tick)
+ self.coroutines[id] = 0
+
+ async def update_text(self):
+ while not self.done:
+ await asyncio.sleep(self.tick)
+ if int(self.loop.time() + self.tick) % 2:
+ text = "⚙️ ...Calculating prime numbers... ⚙️"
+ else:
+ text = "👩‍💻 ...Hacking the universe... 👩‍💻"
+ self.window.widget_outer_text.setText(text)
+
+ self.window.widget_outer_text.setText(
+ "🥳 Congratulations! You found all the prime numbers and solved mathematics. 🥳"
+ )
+
+
+if __name__ == "__main__":
+ rows = 40
+ cols = 40
+ num = rows * cols
+
+ app = QApplication(sys.argv)
+ main_window = MainWindow(rows, cols)
+ eratosthenes = Eratosthenes(num, main_window)
+
+ main_window.show()
+
+ QtAsyncio.run(eratosthenes.start(), handle_sigint=True)
diff --git a/examples/async/eratosthenes/eratosthenes.py b/examples/async/eratosthenes/eratosthenes_trio.py
index 5fb2a35be..89d3d1ce6 100644
--- a/examples/async/eratosthenes/eratosthenes.py
+++ b/examples/async/eratosthenes/eratosthenes_trio.py
@@ -61,7 +61,7 @@ class MainWindow(QMainWindow):
widget.setPalette(palette)
-class Eratosthenes():
+class Eratosthenes(QObject):
""" This Sieve of Eratosthenes runs on a configurable tick (default
0.1 seconds). At each tick, a new subroutine will be created
@@ -69,6 +69,7 @@ class Eratosthenes():
these subroutines also operates on the same tick. """
def __init__(self, num, window, tick=0.1):
+ super().__init__()
self.num = num
self.sieve = [True] * self.num
self.base = 0
@@ -119,14 +120,12 @@ class Eratosthenes():
class AsyncHelper(QObject):
- trigger_signal = Signal()
-
class ReenterQtObject(QObject):
""" This is a QObject to which an event will be posted, allowing
Trio to resume when the event is handled. event.fn() is the
next entry point of the Trio event loop. """
def event(self, event):
- if event.type() == QEvent.User + 1:
+ if event.type() == QEvent.Type.User + 1:
event.fn()
return True
return False
@@ -135,16 +134,17 @@ class AsyncHelper(QObject):
""" This is the QEvent that will be handled by the ReenterQtObject.
self.fn is the next entry point of the Trio event loop. """
def __init__(self, fn):
- super().__init__(QEvent.Type(QEvent.User + 1))
+ super().__init__(QEvent.Type(QEvent.Type.User + 1))
self.fn = fn
- def __init__(self, entry=None):
+ def __init__(self, worker, entry):
super().__init__()
self.reenter_qt = self.ReenterQtObject()
self.entry = entry
- def set_entry(self, entry):
- self.entry = entry
+ self.worker = worker
+ if hasattr(self.worker, "start_signal") and isinstance(self.worker.start_signal, Signal):
+ self.worker.start_signal.connect(self.launch_guest_run)
@Slot()
def launch_guest_run(self):
@@ -183,7 +183,7 @@ if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow(rows, cols)
eratosthenes = Eratosthenes(num, main_window)
- async_helper = AsyncHelper(entry=eratosthenes.start)
+ async_helper = AsyncHelper(eratosthenes, eratosthenes.start)
# This establishes the entry point for the Trio guest run. It varies
# depending on how and when its event loop is to be triggered, e.g.,
diff --git a/examples/async/eratosthenes/requirements.txt b/examples/async/eratosthenes/requirements_trio.txt
index e2cc10204..e2cc10204 100644
--- a/examples/async/eratosthenes/requirements.txt
+++ b/examples/async/eratosthenes/requirements_trio.txt
diff --git a/examples/async/minimal/doc/minimal.rst b/examples/async/minimal/doc/minimal.rst
index dcab2fe29..54f847ea9 100644
--- a/examples/async/minimal/doc/minimal.rst
+++ b/examples/async/minimal/doc/minimal.rst
@@ -1,18 +1,21 @@
-Async examples
-==============
+Async "Minimal" Example
+=======================
The Python language provides keywords for asynchronous operations, i.e.,
"async" to define coroutines or "await" to schedule asynchronous calls in the
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.
-One such package is `trio`. 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. trio offers a dedicated `low-level API
+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.
+
+`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`:
@@ -29,13 +32,7 @@ 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 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
-event loop into a separate class, `minimal` demonstrates that async functions
-can be integrated into any class, including subclasses of Qt classes.
+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
+classes.
diff --git a/examples/async/minimal/minimal.pyproject b/examples/async/minimal/minimal.pyproject
index 97ff6dbc5..b4b1a3f80 100644
--- a/examples/async/minimal/minimal.pyproject
+++ b/examples/async/minimal/minimal.pyproject
@@ -1,3 +1,3 @@
{
- "files": ["minimal.py"]
+ "files": ["minimal_trio.py", "minimal_asyncio.py"]
}
diff --git a/examples/async/minimal/minimal_asyncio.py b/examples/async/minimal/minimal_asyncio.py
new file mode 100644
index 000000000..a6c4708b3
--- /dev/null
+++ b/examples/async/minimal/minimal_asyncio.py
@@ -0,0 +1,40 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Qt
+from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget)
+
+import PySide6.QtAsyncio as QtAsyncio
+
+import asyncio
+import sys
+
+
+class MainWindow(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ widget = QWidget()
+ self.setCentralWidget(widget)
+
+ layout = QVBoxLayout(widget)
+
+ self.text = QLabel("The answer is 42.")
+ layout.addWidget(self.text, alignment=Qt.AlignmentFlag.AlignCenter)
+
+ async_trigger = QPushButton(text="What is the question?")
+ async_trigger.clicked.connect(lambda: asyncio.ensure_future(self.set_text()))
+ layout.addWidget(async_trigger, alignment=Qt.AlignmentFlag.AlignCenter)
+
+ async def set_text(self):
+ await asyncio.sleep(1)
+ self.text.setText("What do you get if you multiply six by nine?")
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ main_window = MainWindow()
+ main_window.show()
+
+ QtAsyncio.run(handle_sigint=True)
diff --git a/examples/async/minimal/minimal.py b/examples/async/minimal/minimal_trio.py
index 1769a2dfc..5ae19fa36 100644
--- a/examples/async/minimal/minimal.py
+++ b/examples/async/minimal/minimal_trio.py
@@ -13,10 +13,10 @@ import trio
class MainWindow(QMainWindow):
- def __init__(self, async_signal):
- super().__init__()
+ start_signal = Signal()
- self.async_signal = async_signal
+ def __init__(self):
+ super().__init__()
widget = QWidget()
self.setCentralWidget(widget)
@@ -32,7 +32,7 @@ class MainWindow(QMainWindow):
@Slot()
def async_start(self):
- self.async_signal.emit()
+ self.start_signal.emit()
async def set_text(self):
await trio.sleep(1)
@@ -41,14 +41,12 @@ class MainWindow(QMainWindow):
class AsyncHelper(QObject):
- trigger_signal = Signal()
-
class ReenterQtObject(QObject):
""" This is a QObject to which an event will be posted, allowing
Trio to resume when the event is handled. event.fn() is the
next entry point of the Trio event loop. """
def event(self, event):
- if event.type() == QEvent.User + 1:
+ if event.type() == QEvent.Type.User + 1:
event.fn()
return True
return False
@@ -57,16 +55,17 @@ class AsyncHelper(QObject):
""" This is the QEvent that will be handled by the ReenterQtObject.
self.fn is the next entry point of the Trio event loop. """
def __init__(self, fn):
- super().__init__(QEvent.Type(QEvent.User + 1))
+ super().__init__(QEvent.Type(QEvent.Type.User + 1))
self.fn = fn
- def __init__(self, entry=None):
+ def __init__(self, worker, entry):
super().__init__()
self.reenter_qt = self.ReenterQtObject()
self.entry = entry
- def set_entry(self, entry):
- self.entry = entry
+ self.worker = worker
+ if hasattr(self.worker, "start_signal") and isinstance(self.worker.start_signal, Signal):
+ self.worker.start_signal.connect(self.launch_guest_run)
@Slot()
def launch_guest_run(self):
@@ -99,15 +98,8 @@ class AsyncHelper(QObject):
if __name__ == "__main__":
app = QApplication(sys.argv)
- async_helper = AsyncHelper()
- main_window = MainWindow(async_helper.trigger_signal)
- async_helper.set_entry(main_window.set_text)
-
- # This establishes the entry point for the Trio guest run. It varies
- # depending on how and when its event loop is to be triggered, e.g.,
- # at a specific moment like a button press (as here) or rather from
- # the beginning.
- async_helper.trigger_signal.connect(async_helper.launch_guest_run)
+ main_window = MainWindow()
+ async_helper = AsyncHelper(main_window, main_window.set_text)
main_window.show()
diff --git a/examples/async/minimal/requirements.txt b/examples/async/minimal/requirements_trio.txt
index ae0d704f0..ae0d704f0 100644
--- a/examples/async/minimal/requirements.txt
+++ b/examples/async/minimal/requirements_trio.txt