PyQt:当计算时间过长时发出两次信号

时间:2017-06-22 09:07:59

标签: python pyqt pyqt5 signals-slots qspinbox

我使用了两个小部件:QSpinBoxQLineEditvalueChanged窗口小部件的QSpinBox插槽已连接到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))

1 个答案:

答案 0 :(得分:2)

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

<强>更新

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

以下是实现此方法的演示:

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