问题
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()
withfunctools.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