PyQt线程通信帮助?QThread和QObject

4
阅读搜索后,我尝试生成一个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_())
2个回答

4
连接到 abort_signal 的插槽似乎没有被调用的原因是,跨线程信号默认会被排队。这意味着信号将被包装成一个事件并发布到接收者所在的任何线程的事件队列中。
在您的特定示例中,接收者是已移动到工作线程的工作对象。在工作线程上调用 start() 将启动其事件循环,这就是 abort_signal 将被排队的位置。但是,工作对象的 run() 方法启动了一个 for 循环,这将像在主 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”按钮,等待工作人员正常完成。这应该会产生如下输出:
Stopping?!
Got done Sig! Got to 5
stop signalled?

请注意,“stop signalled”是最后输出的 - 也就是说,在run()退出并控制已经返回到线程的事件循环之后。
为了在工作程序运行时处理传入的信号,您需要强制立即处理线程的待处理事件。可以像这样完成:
     for step in range(self.maxIters):
        QApplication.processEvents()
        ...

有了这个设置,你应该能看到像这样的输出:
Stopping?!
stop signalled?
Got done Sig! Got to 2

这大概是你想要的意思。

很高兴知道!这正是我认为可能发生的事情,但不知道如何“修复”。QApplication.processEvents()对我来说是未知的。添加到代码中让我担心它会“阻塞”GUI仍然响应 - 但似乎并没有这样做。感谢您提供的信息。thread.quit和thread.terminate之间有什么区别? - Tim Carnahan
@TimCarnahan。quit()槽仅退出线程的事件循环;terminate()会杀死线程而不进行任何清理,通常不安全使用。 - ekhumoro

0

通常情况下,当线程退出run方法时,它将关闭。另一种使普通Python线程关闭的方法是调用其join方法。

对于PyQt,join方法应该是quit或terminate方法。您可能仍然需要将end变量设置为True。


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