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

问题描述 投票:0回答:2

阅读和搜索后,我尝试使用生成 QObject,然后使用 movetoThread 方法运行独立进程并允许 QMainWindow 继续响应。当我尝试在 QThread.run() 方法中实现该操作时,这不起作用。下面的代码是我尝试做一个简单的例子。虽然代码在独立于主窗口的运行线程中工作,但它不会中止。让线程停止的唯一方法是设置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_())
python multithreading pyqt4 signals-slots qthread
2个回答
4
投票

连接到

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 signalled”是输出 last - 即 after

run()
退出并且控制已返回到线程的事件循环。为了在工作线程运行时处理传入信号,您需要强制立即处理线程的挂起事件。这可以这样做:

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

完成后,您应该会看到如下输出:

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

这大概就是您的意图。


0
投票

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

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

© www.soinside.com 2019 - 2024. All rights reserved.