Reputation: 33
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?
Upvotes: 2
Views: 1267
Reputation: 243907
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:
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()
Upvotes: 4