簡體   English   中英

PyQt線程通信有幫助嗎? QThread和QObject

[英]PyQt thread communication help? QThread and QObject

在讀取和搜索之后,我嘗試使用生成QObject,然后使用movetoThread方法運行獨立進程並允許QMainWindow繼續響應。 當我嘗試在QThread.run()方法中實現該操作時,這沒有用。 以下代碼是我嘗試做一個簡單的例子。 雖然代碼在獨立於MainWindow的運行線程中工作,但它不會中止。 我可以讓線程停止的唯一方法是設置worker.end = True。 我認為不應該這樣做。

"""
This is a program to test Threading with Objects in PyQt4.
"""

from time import sleep
import sys

from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QThread
from PyQt4.QtGui import QMainWindow, QApplication, QProgressBar
from PyQt4.QtGui import QPushButton, QVBoxLayout, QWidget

class workerObject(QObject):
    bar_signal = pyqtSignal(int)
    res_signal = pyqtSignal(str)
    term_signal = pyqtSignal()

    def __init__(self, maxIters):
        super(workerObject, self).__init__()
        self.maxIters = maxIters

    def run(self):
        self.bar_signal.emit(self.maxIters)        
        sleep(1)
        self.end = False

        for step in range(self.maxIters):
            if self.end:
                self.maxIters = step
                break
            self.bar_signal.emit(step)
            sleep(2)

        self.res_signal.emit("Got to {}".format(self.maxIters)) 
        self.term_signal.emit()

    @pyqtSlot()
    def mystop(self):
        print "stop signalled?"
        self.end = True

class MCwindow(QMainWindow):
    abort_signal = pyqtSignal(name='abort_signal')

    def __init__(self):
        super(MCwindow,self).__init__()        
        self.maxIters = 50

        widget = QWidget()
        layout = QVBoxLayout(widget)
        self.go_btn = QPushButton()
        self.go_btn.setText('Go')
        layout.addWidget(self.go_btn)
        self.abort_btn = QPushButton()
        self.abort_btn.setText('Stop')
        layout.addWidget(self.abort_btn)
        self.simulation_bar = QProgressBar()
        self.simulation_bar.setRange(0, self.maxIters)
        self.simulation_bar.setFormat("%v")
        layout.addWidget(self.simulation_bar)
        self.setCentralWidget(widget)

        self.go_btn.clicked.connect(self.run_mc)
        # The button calls the windows method to stop --- it could 
        # be that is 'clicked' calls the worker.mystop
#        self.abort_btn.clicked.connect(self.stop_mc)
        # This allows for the abort button to do somethign in the MainWindow
        # before the abort_signal is sent, this works
        self.abort_btn.clicked.connect(self.stop_mc)

    def run_mc(self):        
        self.thread = QThread()                
        self.worker = workerObject(self.maxIters)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        # This is the simple stop method, but does not work
#        self.abort_btn.clicked.connect(self.worker.mystop)
        # This uses the signal in the MCwindow - this connection does NOT works
        self.abort_signal.connect(self.worker.mystop)
        # This does NOT stop the thread
        # and would not allow for any clean up in the worker.
#        self.abort_signal.connect(self.thread.terminate)
        # This is a 'bad' way to stop the woker ... It does, however, work
#        self.abort_signal.connect(self.stopper)
        self.worker.bar_signal.connect(self.setBar)
        self.worker.res_signal.connect(self.setData)
        self.worker.term_signal.connect(self.thread.terminate)
        self.thread.start()

    def stop_mc(self):
        print "Stopping?!"
        # This signal is NEVER seen by the Worker.
        self.abort_signal.emit()

    def stopper(self):
        print "I should stop?!"
        # Should use signals to tell the worker to stop - and not setting a attribute
        self.worker.end=True

    @pyqtSlot(int)
    def setBar(self, val):
        self.simulation_bar.setValue(val)

    @pyqtSlot(str)    
    def setData(self, txt):
        print "Got done Sig!", txt

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MCwindow()
    window.show()
    sys.exit(app.exec_())

連接到abort_signal的插槽似乎沒有被調用的原因是因為跨線程信號默認排隊 這意味着信號將作為事件包裝並發布到接收器所處的任何線程的事件隊列中。

在您的特定示例中,接收器是一個已移動到工作線程的工作對象。 在工作線程上調用start()將啟動其事件循環,這就是abort_signal將排隊的位置。 但是,worker對象的run()方法啟動一個for loop ,這將阻止線程的事件處理,就像它在主gui線程中執行時一樣!

如果您對示例進行一些調整,您可以更清楚地了解發生了什么:

class MCwindow(QMainWindow):
    abort_signal = pyqtSignal(name='abort_signal')

    def __init__(self):
        super(MCwindow,self).__init__()
        # use a sane default
        self.maxIters = 5
        ...
        # DO NOT use QThread.terminate
        self.worker.term_signal.connect(self.thread.quit)

現在運行該示例,然后單擊Go按鈕,單擊Stop按鈕,等待worker正常完成。 這應該產生這樣的輸出:

Stopping?!
Got done Sig! Got to 5
stop signalled?

請注意,“stop signaled” 最后輸出 - 即 run()退出並且控制返回到線程的事件循環之后。 為了在工作程序運行時處理進入的信號,您需要強制立即處理線程的掛起事件。 這可以這樣做:

     for step in range(self.maxIters):
        QApplication.processEvents()
        ...

有了這個,你應該看到這樣的輸出:

Stopping?!
stop signalled?
Got done Sig! Got to 2

這可能是你想要的。

通常,線程在退出run方法時將關閉。 獲取常規python線程的另一種方法是調用它的join方法。

對於PyQt,join方法應該是quit或terminate方法。 您可能仍應將結束變量設置為True。

暫無
暫無

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

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