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

时间:2015-12-09 08:55:29

标签: python multithreading pyqt4 interrupt messagebox

我有以下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 个答案:

答案 0 :(得分:6)

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

尽管如此,你的时间有点偏差。您甚至可以在显示窗口之前创建应用程序并启动线程。因此,消息框将在 之前弹出 ,主窗口甚至会弹出。要获得正确的事件序列,您应该将线程作为MainWindow的run方法的一部分启动, 后,您已经使主窗口可见。如果您希望等待条件与消息设置分开,则可能需要单独的信号和插槽来处理。

答案 1 :(得分:2)

您无法在QDialog内显示QThread。所有GUI相关的东西必须在GUI线程(创建QApplication对象的线程)中完成。你可以做的是使用2 QThread

  • 1st:执行阶段1 。您可以将此finished的{​​{1}}信号连接到QThread中将显示弹出窗口的插槽(使用QMainWindow,以便它是模态的)。
  • 第二名:执行阶段2 。在上面显示的弹出窗口关闭后,您可以创建QDialog.exec_()

答案 2 :(得分:0)

您的线程可以向主窗口发出信号以显示对话框。 如果您不想在对话框打开时关闭线程,则线程可以进入while循环进行等待。在while循环中,它可以在对话框完成后连续检查主线程可以设置为true的变量。 这可能不是最干净的解决方案,但它应该有效。

为了澄清我的答案,我添加了一些伪代码。您需要关注的是如何共享string[] dateOperation = new string[4]; string[] typeOperation = new string[4]; string[] bought = new string[4]; string[] sold = new string[4]; for (int i = 0; i <= 3; i++) { if (tb[i].Text != "" && Odate[i].Text != "") { dateOperation[i] = Odate[i].Text.ToString(); typeOperation[i] = type[i].Text.ToString(); bought[i] = tb[i].Text.ToString(); sold[i] = BGN[i].Text.ToString(); } } string connectionString = @"Data Source=C:\Users\FluksikartoN\Documents\BFDB.sdf;Password=******"; using (SqlCeConnection connection = new SqlCeConnection(connectionString)) { SqlCeCommand cmd = new SqlCeCommand("INSERT INTO Operations (date, type, bought, sold) VALUES (@date, @type, @bought, @sold)"); cmd.CommandType = CommandType.Text; cmd.Connection = connection; cmd.Parameters.AddWithValue("@date", DbType.String); cmd.Parameters.AddWithValue("@type", DbType.String); cmd.Parameters.AddWithValue("@bought", DbType.String); cmd.Parameters.AddWithValue("@sold", DbType.String); connection.Open(); for (int i = 0; i < bought.Length; i++) { cmd.Parameters["@date"].Value = dateOperation[i]; cmd.Parameters["@type"].Value = typeOperation[i]; cmd.Parameters["@bought"].Value = bought[i]; cmd.Parameters["@sold"].Value = sold[i]; cmd.ExecuteNonQuery(); } 变量。你可以,例如使用线程类的成员变量。

dialog_closed

答案 3 :(得分:0)

我最近不得不解决这个问题,做了一些研究并发现了an elegant technique that seems to work reliably。我不需要那里详细说明的全部复杂性,因此这里是我所采取步骤的概述。

我的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方法,并发出问题信号。收到信号后,答案将作为属性值存储,然后退出循环。循环仍被其调用方引用,该调用方可以在实例被垃圾回收之前安全地访问result属性。