How can I change the interval of a Qtimer inside of a Qthread?

杀马特。学长 韩版系。学妹 提交于 2020-06-28 03:58:11

问题


I want to be able to change the interval time of a QTimer inside of a QThread. This is my code.

import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, QTimer, QThread


class Worker(QObject):
    def __init__(self):
        QObject.__init__(self)
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.work)

    def start(self):
        self.timer.start(1000)

    def work(self):
        print("Hello World...")

    def set_interval(self, interval):
        self.timer.setInterval(interval)


def main():
    # Set up main window
    app = QApplication(sys.argv)
    win = QMainWindow()
    win.setFixedSize(200, 100)
    spinbox_interval = QtWidgets.QSpinBox(win)
    spinbox_interval.setMaximum(5000)
    spinbox_interval.setSingleStep(500)
    spinbox_interval.setValue(1000)

    worker = Worker()
    thread = QThread()
    worker.moveToThread(thread)
    thread.started.connect(worker.start)
    thread.start()

    def change_interval():
        value = spinbox_interval.value()
        worker.set_interval(value)

    spinbox_interval.valueChanged.connect(change_interval)

    win.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

If I call worker.setInterval() after starting the timer, the timeout signal no longer sends out a signal. Can someone explain to me what I'm doing wrong?


回答1:


To understand the problem you must run in the console / CMD to get the error message and thus understand the cause, if you do this you get the following error message:

QObject::killTimer: Timers cannot be stopped from another thread
QObject::startTimer: Timers cannot be started from another thread

To understand this error message you must know that:

  • QObjects are not thread-safe so they cannot be modified from another thread,
  • The children of the QObjects live in the same thread as the parent.

So the timer being Worker's children then lives in the secondary thread as its parent, and therefore you cannot modify it from another thread. In this case it is to send modify the information in the secondary thread, and for this there are several options:

  • QMetaObject.invokeMethod() with pyqtSlot:

    import sys
    from PyQt5.QtWidgets import QApplication, QMainWindow, QSpinBox
    from PyQt5.QtCore import pyqtSlot, QMetaObject, QObject, Qt, QTimer, QThread, Q_ARG
    
    
    class Worker(QObject):
        def __init__(self):
            QObject.__init__(self)
            self.timer = QTimer(self)
            self.timer.timeout.connect(self.work)
    
        def start(self):
            self.timer.start(1000)
    
        def work(self):
            print("Hello World...")
    
        @pyqtSlot(int)
        def set_interval(self, interval):
            self.timer.setInterval(interval)
    
    
    def main():
        # Set up main window
        app = QApplication(sys.argv)
        win = QMainWindow()
        win.setFixedSize(200, 100)
        spinbox_interval = QSpinBox(win)
        spinbox_interval.setMaximum(5000)
        spinbox_interval.setSingleStep(500)
        spinbox_interval.setValue(1000)
    
        worker = Worker()
        thread = QThread()
        worker.moveToThread(thread)
        thread.started.connect(worker.start)
        thread.start()
    
        def change_interval():
            value = spinbox_interval.value()
            QMetaObject.invokeMethod(
                worker, "set_interval", Qt.QueuedConnection, Q_ARG(int, value)
            )
    
        spinbox_interval.valueChanged.connect(change_interval)
    
        win.show()
        ret = app.exec_()
    
        QMetaObject.invokeMethod(worker.timer, "stop")
        thread.quit()
        thread.wait()
        sys.exit(ret)
    
    
    if __name__ == "__main__":
        main()
    
  • A custom signal with slot:

    import sys
    from PyQt5.QtWidgets import QApplication, QMainWindow, QSpinBox
    from PyQt5.QtCore import pyqtSignal, pyqtSlot, QMetaObject, QObject, Qt, QTimer, QThread
    
    
    class Worker(QObject):
        updateInterval = pyqtSignal(int)
    
        def __init__(self):
            QObject.__init__(self)
            self.timer = QTimer(self)
            self.timer.timeout.connect(self.work)
    
            self.updateInterval.connect(self.set_interval)
    
        def start(self):
            self.timer.start(1000)
    
        def work(self):
            print("Hello World...")
    
        @pyqtSlot(int)
        def set_interval(self, interval):
            self.timer.setInterval(interval)
    
    
    def main():
        # Set up main window
        app = QApplication(sys.argv)
        win = QMainWindow()
        win.setFixedSize(200, 100)
        spinbox_interval = QSpinBox(win)
        spinbox_interval.setMaximum(5000)
        spinbox_interval.setSingleStep(500)
        spinbox_interval.setValue(1000)
    
        worker = Worker()
        thread = QThread()
        worker.moveToThread(thread)
        thread.started.connect(worker.start)
        thread.start()
    
        def change_interval():
            value = spinbox_interval.value()
            worker.updateInterval.emit(value)
    
        spinbox_interval.valueChanged.connect(change_interval)
    
        win.show()
        ret = app.exec_()
    
        QMetaObject.invokeMethod(worker.timer, "stop")
        thread.quit()
        thread.wait()
        sys.exit(ret)
    
    
    if __name__ == "__main__":
        main()
    
  • Custom QEvent:

    import sys
    from PyQt5.QtWidgets import QApplication, QMainWindow, QSpinBox
    from PyQt5.QtCore import QEvent, QMetaObject, QObject, Qt, QTimer, QThread
    
    
    class IntervalEvent(QEvent):
        def __init__(self, interval):
            QEvent.__init__(self, QEvent.User + 1000)
            self._interval = interval
    
        @property
        def interval(self):
            return self._interval
    
    
    class Worker(QObject):
        def __init__(self):
            QObject.__init__(self)
            self.timer = QTimer(self)
            self.timer.timeout.connect(self.work)
    
        def start(self):
            self.timer.start(1000)
    
        def work(self):
            print("Hello World...")
    
        def set_interval(self, interval):
            self.timer.setInterval(interval)
    
        def event(self, e):
            if isinstance(e, IntervalEvent):
                self.set_interval(e.interval)
            return Worker.event(self, e)
    
    
    def main():
        # Set up main window
        app = QApplication(sys.argv)
        win = QMainWindow()
        win.setFixedSize(200, 100)
        spinbox_interval = QSpinBox(win)
        spinbox_interval.setMaximum(5000)
        spinbox_interval.setSingleStep(500)
        spinbox_interval.setValue(1000)
    
        worker = Worker()
        thread = QThread()
        worker.moveToThread(thread)
        thread.started.connect(worker.start)
        thread.start()
    
        def change_interval():
            value = spinbox_interval.value()
            event = IntervalEvent(value)
            QApplication.postEvent(worker, event)
    
        spinbox_interval.valueChanged.connect(change_interval)
    
        win.show()
        ret = app.exec_()
    
        QMetaObject.invokeMethod(worker.timer, "stop")
        thread.quit()
        thread.wait()
        sys.exit(ret)
    
    
    if __name__ == "__main__":
        main()
    
  • QTimer.singleShot() with functools.partial() (only works with PyQt5, not with PySide2)

    import sys
    from functools import partial
    
    from PyQt5.QtWidgets import QApplication, QMainWindow, QSpinBox
    from PyQt5.QtCore import QMetaObject, QObject, Qt, QTimer, QThread
    
    
    class Worker(QObject):
        def __init__(self):
            QObject.__init__(self)
            self.timer = QTimer(self)
            self.timer.timeout.connect(self.work)
    
        def start(self):
            self.timer.start(1000)
    
        def work(self):
            print("Hello World...")
    
        def set_interval(self, interval):
            print(interval)
            self.timer.setInterval(interval)
    
    
    def main():
        # Set up main window
        app = QApplication(sys.argv)
        win = QMainWindow()
        win.setFixedSize(200, 100)
        spinbox_interval = QSpinBox(win)
        spinbox_interval.setMaximum(5000)
        spinbox_interval.setSingleStep(500)
        spinbox_interval.setValue(1000)
    
        worker = Worker()
        thread = QThread()
        worker.moveToThread(thread)
        thread.started.connect(worker.start)
        thread.start()
    
        def change_interval():
            value = spinbox_interval.value()
            wrapper = partial(worker.set_interval, value)
            QTimer.singleShot(0, wrapper)
    
        spinbox_interval.valueChanged.connect(change_interval)
    
        win.show()
        ret = app.exec_()
    
        QMetaObject.invokeMethod(worker.timer, "stop")
        thread.quit()
        thread.wait()
        sys.exit(ret)
    
    
    if __name__ == "__main__":
        main()
    


来源:https://stackoverflow.com/questions/62314233/how-can-i-change-the-interval-of-a-qtimer-inside-of-a-qthread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!