簡體   English   中英

PyQt4:如何在發出信號之前暫停線程?

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

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

這段代碼現在的作用是按下Start按鈕后,將執行線程中運行的函數。 但是,實際上在功能運行中,測量有兩個階段。 現在我用了一段時間。

但我想要實現的是在“階段1”測量完成之后。 將彈出一個消息框,同時線程將被暫停/保持。 在用戶關閉消息框之前,線程功能將恢復。

使用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方法的一部分啟動。 如果您希望等待條件與消息設置分開,您可能需要一個單獨的信號和插槽來處理它。

您無法在QThread顯示QDialog 所有GUI相關的東西必須在GUI線程(創建QApplication對象的線程)中完成。 你可以做的是使用2 QThread

  • 1st:執行階段1 您可以將此QThreadfinished信號連接到QMainWindow中將顯示彈出窗口的插槽(使用QDialog.exec_()因此它將是模態的)。
  • 第二:執行階段2。 在上面顯示的彈出窗口關閉后創建QThread

您的線程可以向主窗口發出信號以顯示對話框。 如果您不想在對話框打開時關閉線程,則線程可以進入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

我最近不得不解決這個問題,做了一些研究,發現了一種看似可靠的優雅技術 我不需要那里詳細的完整復雜性,所以這里是我采取的步驟的概述。

我的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隱藏在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方法並發出問號信號。 當接收到信號時,答案被存儲為屬性值並且循環退出。 循環仍由其調用者引用,調用者可以在實例進行垃圾回收之前安全地訪問結果屬性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM