PyQt:计算太长时发出两次信号

Posted

技术标签:

【中文标题】PyQt:计算太长时发出两次信号【英文标题】:PyQt: signal emitted twice when calculations are too long 【发布时间】:2017-11-25 11:21:38 【问题描述】:

我使用两个小部件:QSpinBoxQLineEditQSpinBox 小部件的valueChanged 插槽连接到update 函数。该函数由一个耗时的处理(一个带有计算的循环或一个time.sleep() 调用)和一个QLineEdit.setText() 调用组成。一开始,我认为它按预期工作,但我注意到当计算需要很长时间时,信号似乎被发射了两次。

下面是代码:

import time

from PyQt5.QtWidgets import QWidget, QSpinBox, QVBoxLayout, QLineEdit


class Window(QWidget):
    def __init__(self):
        # parent constructor
        super().__init__()

        # widgets
        self.spin_box = QSpinBox()
        self.line_edit = QLineEdit()

        # layout
        v_layout = QVBoxLayout()
        v_layout.addWidget(self.spin_box)
        v_layout.addWidget(self.line_edit)

        # signals-slot connections
        self.spin_box.valueChanged.connect(self.update)

        #
        self.setLayout(v_layout)
        self.show()

    def update(self, param_value):
        print('update')
        # time-consuming part
        time.sleep(0.5) # -> double increment
        #time.sleep(0.4) # -> works normally!
        self.line_edit.setText(str(param_value))


if __name__ == '__main__':
    from PyQt5.QtWidgets import QApplication
    import sys

    app = QApplication(sys.argv)
    win = Window()
    sys.exit(app.exec_())

update的另一个版本:

# alternative version, calculations in a loop instead of time.sleep()
# -> same behaviour
def update2(self, param_value):
    print('update2')
    for i in range(2000000): # -> double increment
        x = i**0.5 * i**0.2
    #for i in range(200000): # -> works normally!
    #    x = i**0.5 * i**0.2
    self.line_edit.setText(str(param_value))

【问题讨论】:

我尝试了相同的代码,结果相同,并且 param_value 的递增?什么鬼......我真的很想看到答案:) 这特别烦人,因为如果你只是减小范围参数,它就可以工作......删除一个零就可以了。 我用time.sleep代替time.sleep(0.4),它可以工作,而time.sleep(0.5)和更多则不能 我得到了同样的结果。我从来没有听说过这样的限制,我一定是做错了什么...... 【参考方案1】:

这里没有真正的奥秘。如果单击旋转框按钮,该值将增加一步。但是,如果您按住按钮,它将不断增加值。为了区分单击和按下/按住,使用了计时器。据推测,阈值约为半秒。因此,如果您插入一个小的额外延迟,则单击可能会被解释为短按/按住,因此旋转框将增加两步而不是一步。

更新

解决此问题的一种方法是在工作线程中进行处理,从而消除延迟。这样做的主要问题是避免在微调框值更改和行编辑更新之间存在过多的延迟。如果按住旋转框按钮,大量信号事件可能会被工作线程排队。一种简单的方法是等到旋转框按钮被释放后再处理所有这些排队的信号——但这会导致长时间的延迟,而每个值都是单独处理的。更好的方法是压缩事件,以便只处理最近的信号。这仍然会有些滞后,但如果处理时间不太长,它应该会导致可接受的行为。

这是一个实现这种方法的演示:

import sys, time
from PyQt5.QtWidgets import (
    QApplication, QWidget, QSpinBox, QVBoxLayout, QLineEdit,
    )
from PyQt5.QtCore import (
    pyqtSignal, pyqtSlot, Qt, QObject, QThread, QMetaObject,
    )

class Worker(QObject):
    valueUpdated = pyqtSignal(int)

    def __init__(self, func):
        super().__init__()
        self._value = None
        self._invoked = False
        self._func = func

    @pyqtSlot(int)
    def handleValueChanged(self, value):
        self._value = value
        if not self._invoked:
            self._invoked = True
            QMetaObject.invokeMethod(self, '_process', Qt.QueuedConnection)
            print('invoked')
        else:
            print('received:', value)

    @pyqtSlot()
    def _process(self):
        self._invoked = False
        self.valueUpdated.emit(self._func(self._value))

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.spin_box = QSpinBox()
        self.line_edit = QLineEdit()
        v_layout = QVBoxLayout()
        v_layout.addWidget(self.spin_box)
        v_layout.addWidget(self.line_edit)
        self.setLayout(v_layout)
        self.thread = QThread(self)
        self.worker = Worker(self.process)
        self.worker.moveToThread(self.thread)
        self.worker.valueUpdated.connect(self.update)
        self.spin_box.valueChanged.connect(self.worker.handleValueChanged)
        self.thread.start()
        self.show()

    def process(self, value):
        time.sleep(0.5)
        return value

    def update(self, param_value):
        self.line_edit.setText(str(param_value))

if __name__ == '__main__':

    app = QApplication(sys.argv)
    win = Window()
    sys.exit(app.exec_())

【讨论】:

以上是关于PyQt:计算太长时发出两次信号的主要内容,如果未能解决你的问题,请参考以下文章

发出 dataChanged 信号 PyQt5

PyQT 4 fnished() 信号不会发出[重复]

PyQt:当单元格进入 QCalendarWidget 时发出信号

PySide/PyQT5:如何从 QGraphicsItem 发出信号?

PyQt跨线程发出信号

PyQt4多信号单槽的例子:计算复利