diff options
Diffstat (limited to 'examples/async/minimal/minimal_trio.py')
-rw-r--r-- | examples/async/minimal/minimal_trio.py | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/examples/async/minimal/minimal_trio.py b/examples/async/minimal/minimal_trio.py new file mode 100644 index 000000000..5ae19fa36 --- /dev/null +++ b/examples/async/minimal/minimal_trio.py @@ -0,0 +1,107 @@ +# 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.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget) + +import outcome +import signal +import sys +import traceback +import trio + + +class MainWindow(QMainWindow): + + start_signal = Signal() + + 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(self.async_start) + layout.addWidget(async_trigger, alignment=Qt.AlignmentFlag.AlignCenter) + + @Slot() + def async_start(self): + self.start_signal.emit() + + async def set_text(self): + await trio.sleep(1) + self.text.setText("What do you get if you multiply six by nine?") + + +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. """ + 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 Trio 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.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): + """ To use Trio and Qt together, one must run the Trio event + loop as a "guest" inside the Qt "host" event loop. """ + if not self.entry: + raise Exception("No entry point for the Trio guest run was set.") + trio.lowlevel.start_guest_run( + self.entry, + run_sync_soon_threadsafe=self.next_guest_run_schedule, + done_callback=self.trio_done_callback, + ) + + def next_guest_run_schedule(self, fn): + """ This function serves to re-schedule the guest (Trio) event + loop inside the host (Qt) event loop. It is called by Trio + at the end of an event loop run in order to relinquish back + to Qt's event loop. By posting an event on the Qt event loop + that contains Trio's next entry point, it ensures that Trio's + event loop will be scheduled again by Qt. """ + QApplication.postEvent(self.reenter_qt, self.ReenterQtEvent(fn)) + + def trio_done_callback(self, outcome_): + """ This function is called by Trio when its event loop has + finished. """ + if isinstance(outcome_, outcome.Error): + error = outcome_.error + traceback.print_exception(type(error), error, error.__traceback__) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + main_window = MainWindow() + async_helper = AsyncHelper(main_window, main_window.set_text) + + main_window.show() + + signal.signal(signal.SIGINT, signal.SIG_DFL) + app.exec() |