PyQt4: Как приостановить поток до тех пор, пока не выйдет сигнал?
У меня есть следующий pyqtmain.py:
#!/usr/bin/python3
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from pyqtMeasThread import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
self.qt_app = QApplication(sys.argv)
QMainWindow.__init__(self, parent)
buttonWidget = QWidget()
rsltLabel = QLabel("Result:")
self.rsltFiled = QLineEdit()
self.buttonStart = QPushButton("Start")
verticalLayout = QVBoxLayout(buttonWidget)
verticalLayout.addWidget(rsltLabel)
verticalLayout.addWidget(self.rsltFiled)
verticalLayout.addWidget(self.buttonStart)
butDW = QDockWidget("Control", self)
butDW.setWidget(buttonWidget)
self.addDockWidget(Qt.LeftDockWidgetArea, butDW)
self.mthread = QThread() # New thread to run the Measurement Engine
self.worker = MeasurementEngine() # Measurement Engine Object
self.worker.moveToThread(self.mthread)
self.mthread.finished.connect(self.worker.deleteLater) # Cleanup after thread finished
self.worker.measure_msg.connect(self.showRslt)
self.buttonStart.clicked.connect(self.worker.run)
# Everything configured, start the worker thread.
self.mthread.start()
def run(self):
""" Show the window and start the event loop """
self.show()
self.qt_app.exec_() # Start event loop
@pyqtSlot(str)
def showRslt(self, mystr):
self.rsltFiled.setText(mystr)
def main():
win = MainWindow()
win.run()
if __name__ == '__main__':
main()
И еще один поток script выполняет фактическое измерение:
from PyQt4.QtCore import *
import time
class MeasurementEngine(QObject):
measure_msg = pyqtSignal(str)
def __init__(self):
QObject.__init__(self) # Don't forget to call base class constructor
@pyqtSlot()
def run(self):
self.measure_msg.emit('phase1')
time.sleep(2) # here I would like to make it as an interrupt
self.measure_msg.emit('phase2')
Что теперь делает этот код, так как после нажатия кнопки "Пуск" запускается функция, выполняемая в потоке. Однако на самом деле в прогоне функции есть две фазы измерения. Прямо сейчас я использовал задержку времени.
Но то, что я хотел бы реализовать на самом деле, заключается в том, что после измерения "phase1". Появится окно с сообщением, и в то же время поток будет приостановлен/удерживаться. Пока пользователь не закрыл окно сообщения, функция потока будет возобновлена.
Ответы
Ответ 1
Вы не можете отобразить QDialog
в пределах QThread
. Все связанные с GUI вещи должны быть выполнены в потоке графического интерфейса (тот, который создал объект QApplication
). Вы можете использовать 2 QThread
:
- 1st: выполнить phase1. Вы можете подключить сигнал
finished
этого QThread
к слоту в QMainWindow
, который отобразит всплывающее окно (используя QDialog.exec_()
, чтобы он был модальным).
- 2nd: выполнить phase2. Вы создаете
QThread
после того, как всплывающее окно, показанное здесь выше, было закрыто.
Ответ 2
Используйте QWaitCondition
из модуля QtCore
. Используя блокировку мьютекса, вы устанавливаете фоновый поток на ожидание/спящий режим до тех пор, пока поток переднего плана не пробудит его. Затем он продолжит выполнять свою работу оттуда.
#!/usr/bin/python3
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from pyqtMeasThread import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
self.qt_app = QApplication(sys.argv)
QMainWindow.__init__(self, parent)
buttonWidget = QWidget()
rsltLabel = QLabel("Result:")
self.rsltFiled = QLineEdit()
self.buttonStart = QPushButton("Start")
verticalLayout = QVBoxLayout(buttonWidget)
verticalLayout.addWidget(rsltLabel)
verticalLayout.addWidget(self.rsltFiled)
verticalLayout.addWidget(self.buttonStart)
butDW = QDockWidget("Control", self)
butDW.setWidget(buttonWidget)
self.addDockWidget(Qt.LeftDockWidgetArea, butDW)
self.mutex = QMutex()
self.cond = QWaitCondition()
self.mthread = QThread() # New thread to run the Measurement Engine
self.worker = MeasurementEngine(self.mutex, self.cond) # Measurement Engine Object
self.worker.moveToThread(self.mthread)
self.mthread.finished.connect(self.worker.deleteLater) # Cleanup after thread finished
self.worker.measure_msg.connect(self.showRslt)
self.buttonStart.clicked.connect(self.worker.run)
# Everything configured, start the worker thread.
self.mthread.start()
def run(self):
""" Show the window and start the event loop """
self.show()
self.qt_app.exec_() # Start event loop
# since this is a slot, it will always get run in the event loop in the main thread
@pyqtSlot(str)
def showRslt(self, mystr):
self.rsltFiled.setText(mystr)
msgBox = QMessageBox(parent=self)
msgBox.setText("Close this dialog to continue to Phase 2.")
msgBox.exec_()
self.cond.wakeAll()
def main():
win = MainWindow()
win.run()
if __name__ == '__main__':
main()
и
from PyQt4.QtCore import *
import time
class MeasurementEngine(QObject):
measure_msg = pyqtSignal(str)
def __init__(self, mutex, cond):
QObject.__init__(self) # Don't forget to call base class constructor
self.mtx = mutex
self.cond = cond
@pyqtSlot()
def run(self):
# NOTE: do work for phase 1 here
self.measure_msg.emit('phase1')
self.mtx.lock()
try:
self.cond.wait(self.mtx)
# NOTE: do work for phase 2 here
self.measure_msg.emit('phase2')
finally:
self.mtx.unlock()
В то же время ваше время немного сбито. Вы создаете приложение и запускаете поток, прежде чем вы даже покажете свое окно. Таким образом, окно сообщения появится до, даже если всплывет главное окно. Чтобы получить правильную последовательность событий, вы должны начать свой поток как часть метода run
вашего MainWindow, после, которое вы уже сделали основным окном. Если вы хотите, чтобы условие ожидания было отделено от настроек сообщений, вам может понадобиться отдельный сигнал и слот, чтобы справиться с этим.
Ответ 3
Ваш поток может выдать сигнал в главное окно, чтобы отобразить диалог.
Если вы не хотите закрывать поток, пока диалог открыт, поток может входить в цикл while для ожидания. В цикле while он может непрерывно проверять переменную, которую основной поток может установить в true после завершения диалога.
Это может быть не самое чистое решение, но оно должно работать.
Чтобы немного прояснить мой ответ, я добавил несколько псевдокодов. Что вам нужно, так это то, как вы делитесь переменной dialog_closed
. Вы можете, например, используйте переменную-член класса потока.
Thread:
emit_signal
dialog_closed = False
while not dialog_closed:
pass
go_on_with_processing
MainThread:
def SignalRecieved():
open_dialog
dialog_closed = True
Ответ 4
Недавно мне пришлось решить эту проблему, я провел небольшое исследование и обнаружил элегантную технику, которая, кажется, работает надежно. Мне не нужно было подробно описывать всю сложность, поэтому вот краткое описание шагов, которые я предпринял.
Мой класс графического интерфейса определяет, как атрибуты класса, два сигнала.
oyn_sig = pyqtSignal(str) # Request for operator yes/no
ryn_sig = pyqtSignal(bool) # Response to yes/no request
Внутри метода, который инициализирует компоненты GUI, этот сигнал связан с обработчиком сигнала экземпляра GUI.
self.oyn_sig.connect(self.operator_yes_no)
Вот код для метода-обработчика GUI:
@pyqtSlot(str)
def operator_yes_no(self, msg):
"Asks the user a 'yes/no question on receipt of a signal then signal a bool answer.'"
answer = QMessageBox.question(None,
"Confirm Test Sucess",
msg,
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
# Signal the caller that the result was received.
self.ryn_sig.emit(answer==QMessageBox.Yes)
Как обычно, графический интерфейс работает в основном потоке, и поэтому он должен сигнализироваться из потока, выполняющего работу в фоновом режиме. В свою очередь, как только он получил ответ оператора, он генерирует ответный сигнал в исходящий поток.
Рабочий поток использует следующую функцию для получения ответа оператора.
def operator_yes_no(self, msg):
loop = LoopSpinner(self.gui, msg)
loop.exec_()
return loop.result
Это создает объект LoopSpinner
и начинает выполнять его цикл событий, тем самым приостанавливая цикл событий текущего потока, пока не завершится "внутренний поток". Большинство LoopSpinner
скрыто внутри класса LoopSpinner
, который, вероятно, должен был быть назван лучше. Вот его определение.
class LoopSpinner(QEventLoop):
def __init__(self, gui, msg):
"Ask for an answer and communicate the result."
QEventLoop.__init__(self)
gui.ryn_sig.connect(self.get_answer)
gui.oyn_sig.emit(msg)
@pyqtSlot(bool)
def get_answer(self, result):
self.result = result
self.quit()
Экземпляр LoopSpinner соединяет ответный сигнал со своим методом get_answer
и излучает сигнал вопроса. Когда сигнал получен, ответ сохраняется как значение атрибута и цикл завершается. На этот цикл все еще ссылается вызывающая сторона, которая может безопасно получить доступ к атрибуту результата до того, как экземпляр будет собран.