Я пишу GUI в Python / PySide2.
Я хочу порождать рабочий поток для выполнения некоторых сложных вычислений (функция определена в отдельном модуле) на основе загруженных входных данных. через GUI.
В зависимости от характера / формата данных, я хотел бы создать окно сообщения с предупреждением, предлагающим пользователю либо
- a ) go назад и загрузите некоторые другие данные, или
- b) в любом случае выполните вычисления.
Тем временем рабочий поток должен ждать ответа пользователя в окне сообщения, прежде чем продолжить работу с кодом.
Исходя из предложений this вопрос , я получил свой код для работы, как нужно, используя QMutex
и QWaitCondition
. Приведенный ниже минимальный воспроизводимый пример показывает текущее состояние, в котором я нахожусь. Само собой разумеется, это чрезвычайно хаки:
QMutex
и QWaitCondition
являются глобальными объектами в GUI файл, но должен быть введен в kwargs целевой функции, чтобы заблокировать поток, пока открыто окно сообщения; - Я использую
QEvent
, созданный вручную, чтобы выяснить, пользователь нажал кнопку да или нет (мне нужно что-то с состоянием, которое я мог бы отправить в основной поток и обратно); - Я добавил пользовательское исключение, так как в противном случае оно не вызывается должным образом внутри try / исключением блок в методе
Worker
run
(не имею никакого представления об этом);
Я явно что-то здесь упускаю. У кого-нибудь есть идеи о том, как добиться того, чего я хочу? Есть ли очевидный дизайн fl aws или какой-то другой модуль PySide, который я должен использовать?
myapp.py:
import sys
from PySide2.QtCore import (QMutex, QObject, QRunnable, QThreadPool,
QWaitCondition, Signal, Slot)
from PySide2.QtWidgets import (QApplication, QLabel, QMainWindow,
QMessageBox, QPushButton, QVBoxLayout,
QWidget)
from myfunctions import computationally_expensive_function
MUTEX = QMutex()
WAIT_CONDITION = QWaitCondition()
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.threadpool = QThreadPool()
self.frame = QWidget(self)
self.setCentralWidget(self.frame)
layout = QVBoxLayout()
self.button = QPushButton(self, text='click me!')
self.button.clicked.connect(self.start_worker)
layout.addWidget(self.button)
self.label = QLabel(self, text='a label')
layout.addWidget(self.label)
self.frame.setLayout(layout)
def start_worker(self):
worker = Worker(func=computationally_expensive_function)
worker.signals.warning_callback.connect(self.some_important_waring)
worker.signals.success.connect(self.worker_finished_gracefully)
worker.signals.error.connect(self.worker_raised_error)
self.threadpool.start(worker)
def some_important_waring(self, warning_tuple):
warning_event, important_message = warning_tuple
reply = QMessageBox.question(self, 'WARNING', important_message,
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
warning_event.accept()
else:
warning_event.ignore()
WAIT_CONDITION.wakeAll()
def worker_finished_gracefully(self, text):
self.label.setText(text)
def worker_raised_error(self, error):
self.label.setText(error)
class Worker(QRunnable):
def __init__(self, func, *args, **kwargs):
super().__init__()
self.func = func
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['warning_callback'] = self.signals.warning_callback
self.kwargs['mutex'] = MUTEX
self.kwargs['wait_condition'] = WAIT_CONDITION
@Slot()
def run(self):
try:
return_value = self.func(*self.args, **self.kwargs)
except Exception as e:
error_message = str(e)
self.signals.error.emit(error_message)
else:
self.signals.success.emit(return_value)
class WorkerSignals(QObject):
warning_callback = Signal(tuple)
error = Signal(str)
success = Signal(str)
if __name__ == '__main__':
app = QApplication()
gui = MyApp()
gui.show()
sys.exit(app.exec_())
myfunctions. py:
import random
from PySide2.QtCore import QEvent
def computationally_expensive_function(*args, **kwargs):
# some preprocessing is performed here
warning_callback = kwargs.get('warning_callback')
mutex = kwargs.get('mutex')
wait_condition = kwargs.get('wait_condition')
if random.random() > 0.5: # simulates issues with the data passed
mutex.lock()
warning_event = QEvent(QEvent.WindowActivate)
important_message = 'This is an important message! Will you continue?'
warning_callback.emit((warning_event, important_message))
wait_condition.wait(mutex)
mutex.unlock()
if not warning_event.isAccepted():
raise AbortedByUser('User decided to abort the computation')
return 'End of computationally expensive function'
class AbortedByUser(Exception):
"""Raised if the user decides to abort the computation"""