0

I have a QTimer to do stuff at intervals. Sometimes, this stuff raises an exception. When that happens, I must stop the QTimer without destroying it, so I can restart it later on. However, when I attempt to stop it from within its timeout event, it doesn't stop immediately, but rather keeps ticking multiple times for a short period before stopping, and produces a Timers cannot be stopped from another thread error.

What is the proper way to make the timer stop itself immediately, without errors?

Here's my demo code:

import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *


class MyWorker(QObject):
    def __init__(self):
        super().__init__()
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.tick)

    def begin(self):
        self.timer.start(1)

    def tick(self):
        try:
            print("error raised")
            raise Exception()
        except:
            print("error caught, stopping")
            self.timer.stop()


class MainWindow(QMainWindow):
    startSignal = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.myThread = QThread()
        self.myWorker = MyWorker()
        self.myWorker.moveToThread(self.myThread)
        self.startSignal.connect(self.myWorker.begin)
        self.myThread.start()
        self.startSignal.emit()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())

and the output:

error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
error raised
error caught, stopping
QObject::killTimer: Timers cannot be stopped from another thread
AgentRev
  • 749
  • 1
  • 8
  • 20
  • 1
    Either connect the `timer.timeout` in the `begin()`, or use the `pyqtSlot()` decorator for `tick`, so that it's actually called in the thread, allowing the timer to stop. – musicamante Nov 02 '22 at 23:33
  • @musicamante Okay, adding the decorator does work. Thanks. I stopped using decorators some time ago specifically because of the main answer to the "Why do I need to decorate" question that was linked says that "in most PyQt applications it is not necessary to use pyqtSlot at all." So I guess this is one of those special scenarios where it is in fact required. – AgentRev Nov 02 '22 at 23:43
  • 1
    It is indeed. Note that this case is specific to PyQt (AFAIR, it should work as expected in PySide) and only happens when connecting a signal *before* moving the sender to another thread, which is exactly your case: the timeout signal is connected in the `__init__` of MyWorker, and at that time it still resides in the main thread. Note that the answer you're referring to specifically refers to cross-thread connections (and links the other answer explaining this aspect), but I can understand the confusion, since at first sight it might not seem the case of your code. – musicamante Nov 02 '22 at 23:53

0 Answers0