简体   繁体   English

PyQt4:如何在发出信号之前暂停线程?

[英]PyQt4: How to pause a Thread until a signal is emitted?

I have the following pyqtmain.py: 我有以下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()

And another thread script performing the actual measurement: 另一个执行实际测量的线程脚本:

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')

What this code does now is that after the Start button is pressed, the function run in the thread will be executed. 这段代码现在的作用是按下Start按钮后,将执行线程中运行的函数。 However, actually in the function run, there are two phases of the measurement. 但是,实际上在功能运行中,测量有两个阶段。 Right now I used an time delay. 现在我用了一段时间。

But what I would like to implement actually is that after the 'phase1' measurement is done. 但我想要实现的是在“阶段1”测量完成之后。 A message box will be popped up, and at the same time, the thread will be paused/held. 将弹出一个消息框,同时线程将被暂停/保持。 Until the user closed the message box, then the thread function will be resumed. 在用户关闭消息框之前,线程功能将恢复。

Use a QWaitCondition from the QtCore module. 使用QtCore模块中的QWaitCondition Using a mutex lock, you set the background thread to wait/sleep until the foreground thread wakes it back up. 使用互斥锁,将后台线程设置为等待/休眠,直到前台线程将其唤醒。 Then it will continue doing its work from there. 然后它将继续从那里开始工作。

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

And: 和:

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()

Your timing is a little bit off in all this though. 尽管如此,你的时间有点偏差。 You create the app and start the thread before you even show your window. 您甚至可以在显示窗口之前创建应用程序并启动线程。 Thus, the message box will pop up before the main window even pops up. 因此,在主窗口弹出之前 ,将弹出消息框。 To get the right sequence of events, you should start your thread as part of the run method of your MainWindow, after you have already made the main window visible. 要获得正确的事件序列,您应该已经使主窗口可见之后 ,将您的线程作为MainWindow的run方法的一部分启动。 If you want the wait condition to be separate from the setting of the messages, you may need a separate signal and slot to deal with that. 如果您希望等待条件与消息设置分开,您可能需要一个单独的信号和插槽来处理它。

You can't display a QDialog from within a QThread . 您无法在QThread显示QDialog All GUI related stuff must be done in the GUI thread (the one that created the QApplication object). 所有GUI相关的东西必须在GUI线程(创建QApplication对象的线程)中完成。 What you could do is to use 2 QThread : 你可以做的是使用2 QThread

  • 1st: perform phase1 . 1st:执行阶段1 You can connect the finished signal of this QThread to a slot in the QMainWindow that will display the popup (using QDialog.exec_() so it will be modal). 您可以将此QThreadfinished信号连接到QMainWindow中将显示弹出窗口的插槽(使用QDialog.exec_()因此它将是模态的)。
  • 2nd: perform phase2 . 第二:执行阶段2。 You create the QThread after the popup shown here above has been closed. 在上面显示的弹出窗口关闭后创建QThread

Your thread can emit a signal to the main window to show the dialog. 您的线程可以向主窗口发出信号以显示对话框。 If you don't want to close the thread while the dialog is open, the thread could enter a while loop for waiting. 如果您不想在对话框打开时关闭线程,则线程可以进入while循环进行等待。 In the while loop it can continuously check a variable which the main thread can set to true after the dialog is finished. 在while循环中,它可以在对话框完成后连续检查主线程可以设置为true的变量。 This might not be the cleanest solution, but it should work. 这可能不是最干净的解决方案,但应该可行。

To clarify my answer a bit, I added some pseudo code. 为了澄清我的答案,我添加了一些伪代码。 What you have to care about is how you share the dialog_closed variable. 您需要关心的是如何共享dialog_closed变量。 You could eg use a member variable of the thread class. 例如,您可以使用线程类的成员变量。

Thread:
emit_signal
dialog_closed = False
while not dialog_closed:
   pass
go_on_with_processing

MainThread:
def SignalRecieved():
   open_dialog
   dialog_closed = True

I recently had to solve pretty much this problem, did a little research and discovered an elegant technique that seems to work reliably . 我最近不得不解决这个问题,做了一些研究,发现了一种看似可靠的优雅技术 I didn't need the full complexity detailed there, so here's an outline of the steps I took. 我不需要那里详细的完整复杂性,所以这里是我采取的步骤的概述。

My GUI class defines, as class attributes, two signals. 我的GUI类将两个信号定义为类属性。

oyn_sig = pyqtSignal(str)       # Request for operator yes/no
ryn_sig = pyqtSignal(bool)      # Response to yes/no request

Inside the method that initialises the GUI components this signal is connected to the GUI instance's signal handler. 在初始化GUI组件的方法中,此信号连接到GUI实例的信号处理程序。

    self.oyn_sig.connect(self.operator_yes_no)

Here's the code for the handler method of the GUI: 这是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)

As usual the GUI is running in the main thread, and so it needs to be signalled from the thread doing the work in the background. 像往常一样,GUI在主线程中运行,因此需要从执行后台工作的线程发出信号。 In turn, once it's received the operator's response it raises a response signal to the originating thread. 反过来,一旦收到操作员的响应,它就会向原始线程发出响应信号。

The worker thread uses the following function to get an operator response. 工作线程使用以下函数来获取操作员响应。

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

This creates a LoopSpinner object and starts executing its event loop, thereby suspend the current thread's event loop until the "inner thread" terminates. 这将创建一个LoopSpinner对象并开始执行其事件循环,从而暂停当前线程的事件循环,直到“内部线程”终止。 Most of the smarts are hidden inside the LoopSpinner class, which should probably have been better named. 大多数LoopSpinner隐藏在LoopSpinner类中,它应该更好地命名。 Here's its definition. 这是它的定义。

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()

A LoopSpinner instance connects the response signal to its get_answer method and emits the question signal. LoopSpinner实例将响应信号连接到其get_answer方法并发出问号信号。 When the signal is received the answer is stored as an attribute value and the loop quits. 当接收到信号时,答案被存储为属性值并且循环退出。 The loop is still referenced by its caller, which can safely access the result attribute before the instance is garbage collected. 循环仍由其调用者引用,调用者可以在实例进行垃圾回收之前安全地访问结果属性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM