PyQt4:如何暂停线程直到发出信号?

11

我有以下 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()

另一个线程脚本执行实际的测量:

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”测量后,会弹出一个消息框,同时线程将被暂停/保持。直到用户关闭消息框,然后线程函数将被恢复。

4个回答

10

使用 QtCore 模块中的 QWaitCondition。使用互斥锁,将后台线程设置为等待/休眠状态,直到前台线程唤醒它。然后它将从那里继续执行其工作。

#!/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()

你的时间轴有点问题。在显示窗口之前,你先创建了应用程序并启动线程。因此,消息框将在主窗口出现之前弹出。为了让事件顺序正确,你应该在MainWindow的run方法中启动你的线程,在主窗口可见后再启动。如果你希望等待条件与消息设置分开处理,可能需要一个单独的信号和插槽来处理它。

2
您不能在一个QThread中显示一个QDialog。所有GUI相关的操作都必须在GUI线程中完成(即创建QApplication对象的线程)。您可以使用两个QThread来实现:
  • 第1个:执行阶段1。您可以将此QThreadfinished信号连接到QMainWindow中的一个插槽,该插槽将显示弹出窗口(使用QDialog.exec_()使其成为模态)。
  • 第2个:执行阶段2。您在上面显示的弹出窗口关闭后创建QThread

1
您的线程可以发出信号到主窗口以显示对话框。如果您不想在对话框打开时关闭线程,线程可以进入一个等待循环。在 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

1
槽和信号的全部意义不就是为了避免无限循环地等待某些事件吗?浪费了每一个进行这种操作的 CPU 周期。 - holdenweb

0

最近我遇到了一个类似的问题,做了一些研究并发现了一种看起来非常可靠的优雅技巧。我不需要其中详细的复杂性,所以这里是我采取的步骤概述。

我的GUI类定义了两个信号作为类属性。

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)

通常情况下,GUI 运行在主线程中,因此需要从后台执行工作的线程发出信号。一旦收到操作员的响应,它会向原始线程发出响应信号。

工作线程使用以下函数获取操作员响应。

def operator_yes_no(self, msg):
    loop = LoopSpinner(self.gui, msg)
    loop.exec_()
    return loop.result

这段代码创建了一个 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 方法并发出问题信号。当接收到信号时,答案将被存储为属性值并退出循环。循环仍由其调用者引用,可以在实例被垃圾回收之前安全地访问结果属性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接