PyQt线程通信有帮助吗? QThread和QObject

时间:2015-10-13 16:53:30

标签: python multithreading pyqt4 qthread

在读取和搜索之后,我尝试使用生成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 个答案:

答案 0 :(得分:4)

连接到abort_signal的插槽似乎没有被调用的原因是因为默认情况下跨线程信号排队。这意味着信号将被包装为事件并发布到接收者所处的任何线程的事件队列中。

在您的特定示例中,接收器是已移动到工作线程的工作对象。在工作线程上调用start()将启动其事件循环,这就是abort_signal将排队的位置。但是,worker对象的run()方法启动for loop,它将阻止线程的事件处理,其执行方式与执行时完全相同在主要的线程!

如果您对示例进行一些调整,您可以更清楚地了解发生了什么:

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?

请注意"停止发出信号"输出 last - 即在 run()退出后并且控制权已返回到线程的事件循环。为了在工作人员运行时处理进入的信号,您需要强制立即处理线程的待处理事件。这可以这样做:

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

有了这个,你应该看到这样的输出:

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

这可能是你想要的。

答案 1 :(得分:0)

通常,线程在退出run方法时会关闭。获取常规python线程的另一种方法是调用它的join方法。

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