diff options
Diffstat (limited to 'examples/async')
-rw-r--r-- | examples/async/eratosthenes/eratosthenes_asyncio.py | 59 | ||||
-rw-r--r-- | examples/async/eratosthenes/eratosthenes_trio.py | 14 | ||||
-rw-r--r-- | examples/async/eratosthenes/requirements_trio.txt (renamed from examples/async/eratosthenes/requirements.txt) | 0 | ||||
-rw-r--r-- | examples/async/minimal/minimal_asyncio.py | 59 | ||||
-rw-r--r-- | examples/async/minimal/minimal_trio.py | 28 | ||||
-rw-r--r-- | examples/async/minimal/requirements_trio.txt (renamed from examples/async/minimal/requirements.txt) | 0 |
6 files changed, 87 insertions, 73 deletions
diff --git a/examples/async/eratosthenes/eratosthenes_asyncio.py b/examples/async/eratosthenes/eratosthenes_asyncio.py index 87d56e6fa..f63058cdf 100644 --- a/examples/async/eratosthenes/eratosthenes_asyncio.py +++ b/examples/async/eratosthenes/eratosthenes_asyncio.py @@ -6,10 +6,8 @@ from PySide6.QtGui import (QColor, QFont, QPalette) from PySide6.QtWidgets import (QApplication, QGridLayout, QLabel, QMainWindow, QVBoxLayout, QWidget) import asyncio -import outcome import signal import sys -import traceback from random import randint @@ -61,7 +59,9 @@ class MainWindow(QMainWindow): widget.setPalette(palette) -class Eratosthenes(): +class Eratosthenes(QObject): + + done_signal = Signal() """ 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,15 +120,17 @@ class Eratosthenes(): "🥳 Congratulations! You found all the prime numbers and solved mathematics. 🥳" ) + # This signals to the guest run when there are no more asyncio tasks + # left so its event loop can finish. + self.done_signal.emit() -class AsyncHelper(QObject): - trigger_signal = Signal() +class AsyncHelper(QObject): 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. """ + 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.User + 1: event.fn() @@ -136,22 +139,26 @@ class AsyncHelper(QObject): class ReenterQtEvent(QEvent): """ This is the QEvent that will be handled by the ReenterQtObject. - self.fn is the next entry point of the Trio event loop. """ + self.fn is the next entry point of the asyncio event loop. """ def __init__(self, fn): super().__init__(QEvent.Type(QEvent.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 self.loop = asyncio.new_event_loop() + self.done = False - 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.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 launch_guest_run(self): + 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: @@ -159,13 +166,23 @@ class AsyncHelper(QObject): 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 restart the asyncio event loop. """ - self.loop.call_soon(self.next_guest_run_schedule) - self.loop.run_forever() + 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 @@ -189,13 +206,13 @@ 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., - # from the beginning (as here) or rather at a specific moment like - # a button press. - QTimer.singleShot(0, async_helper.launch_guest_run) + # 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() diff --git a/examples/async/eratosthenes/eratosthenes_trio.py b/examples/async/eratosthenes/eratosthenes_trio.py index 5fb2a35be..862a4fddd 100644 --- a/examples/async/eratosthenes/eratosthenes_trio.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,8 +120,6 @@ 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 @@ -138,13 +137,14 @@ class AsyncHelper(QObject): super().__init__(QEvent.Type(QEvent.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/minimal_asyncio.py b/examples/async/minimal/minimal_asyncio.py index 337a2558d..497e388a1 100644 --- a/examples/async/minimal/minimal_asyncio.py +++ b/examples/async/minimal/minimal_asyncio.py @@ -5,18 +5,17 @@ from PySide6.QtCore import (Qt, QEvent, QObject, Signal, Slot) from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget) import asyncio -import outcome import signal import sys -import traceback class MainWindow(QMainWindow): - def __init__(self, async_signal): - super().__init__() + start_signal = Signal() + done_signal = Signal() - self.async_signal = async_signal + def __init__(self): + super().__init__() widget = QWidget() self.setCentralWidget(widget) @@ -32,21 +31,20 @@ class MainWindow(QMainWindow): @Slot() def async_start(self): - self.async_signal.emit() + self.start_signal.emit() 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): - 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. """ + 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.User + 1: event.fn() @@ -55,22 +53,26 @@ class AsyncHelper(QObject): class ReenterQtEvent(QEvent): """ This is the QEvent that will be handled by the ReenterQtObject. - self.fn is the next entry point of the Trio event loop. """ + self.fn is the next entry point of the asyncio event loop. """ def __init__(self, fn): super().__init__(QEvent.Type(QEvent.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 self.loop = asyncio.new_event_loop() + self.done = False - 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.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 launch_guest_run(self): + 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: @@ -78,13 +80,23 @@ class AsyncHelper(QObject): 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 restart the asyncio event loop. """ - self.loop.call_soon(self.next_guest_run_schedule) - self.loop.run_forever() + 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 @@ -102,15 +114,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/minimal_trio.py b/examples/async/minimal/minimal_trio.py index 1769a2dfc..526bdd331 100644 --- a/examples/async/minimal/minimal_trio.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,8 +41,6 @@ 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 @@ -60,13 +58,14 @@ class AsyncHelper(QObject): super().__init__(QEvent.Type(QEvent.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 |