python - PyQt线程通信帮助? QThread 和 QObject

标签 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_())

最佳答案

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

在您的特定示例中,接收器是一个工作对象,它已被移动到工作线程。在工作线程上调用 start() 将启动其事件循环,这就是 abort_signal 将排队的地方。但是,worker 对象的 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按钮,等待worker正常完成。这应该产生如下输出:

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

请注意,“停止信号”是 last 的输出 - 即 after run() 退出并且控制权已返回到线程的事件循环.为了在 worker 运行时处理传入信号,您需要强制立即处理线程的未决事件。这可以像这样完成:

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

有了它,您应该会看到这样的输出:

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

这大概是您的意图。

关于python - PyQt线程通信帮助? QThread 和 QObject,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33108337/

相关文章:

python - PyQt4 表用于从剪贴板插入表 - tableWidget?

Python - 从另一个列表中删除一组列表

java - 当 jmeter 中的线程完成时,它会清除它使用的所有内存吗?

qt - PyQt QComboBox 下拉方向

c - 线程实现

java - StoreLoad内存屏障

PyQt_PyObject 在使用新型信号/插槽时等效吗?

python - 使用 Teradata 模块将 Python 与 Teradata 连接

python - 使用 FTPwalker 连接到 FTP 资源失败,错误代码为 "Name or service not known"

Python-将文本列转换为数组,我可以在其中访问数组中的每个数字