pythonqtpyqt5qthreadqtimer

stopping a Qthread with a QTimer


I am starting a Qthread in my GUI to perform an optimization function. I want to include a stopping function that can interrupt the Qthread, and end the optimization function immediately.

I read that using Qthread.terminate() is not recommended; Using a stopping flag is not possible because the nature of the function is not a loop.

I thought about using a QTimer in the QThread (a watchdog timer) that periodically checks a stopping flag, and if it is triggered, just end the optimization function, but I can not really imagine how such an idea can be written.

any Ideas?

class Worker(QObject):
    finished = pyqtSignal()
    def run(self):
        # implement a QTimer here that will somehow interrupt the minimize function
        # minimize is an arbitrary function that takes too long to run, and uses child processes to do so
        result = minimize(something)

FROM_Optimization_Process,_ = loadUiType(os.path.join(os.path.dirname(__file__),"ui_files/Optimization_process_window.ui"))

class Optimization_process_window(QDialog, FROM_Optimization_Process):
    def __init__(self, parent=None):
        # first UI
        super(Optimization_process_window, self).__init__(parent)
        self.setupUi(self)

    def start_solving_thread(self):
        self.thread = QThread()
        self.thread.daemon = True
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()

    def stop_solving(self):
        # implement an interrupt function here
        self.thread.quit()
        self.thread.wait()


Solution

  • You misunderstand how timers work. They cannot "interrupt" a code running in a thread in any way. Timers only can work when event loop in the thread is idle and ready to process the timer events and signal. And that event loop is blocked if some code is running in that thread.

    In other words, QTimer in your thread will not work unless you unblock the event loop time to time to process the timer signal. But from what I see, you probably do some intensive work in your minimize(something) function. And it blocks the event loop completely.

    If you want to be able to implement worker/thread interruption, the only way is to implement interruptions into your minimize(something) function by periodical polling. You need to split the work in this function into certain blocks and after each block is done, you check if the worker/thread is supposed to be stopped.

    QThread has a helper functions for this. It is QThread.requestInterruption() and QThread.isInterruptionRequested(), these functions are thread safe. And you can access the thread instance from your worker by calling QObject.thread(). But it is you responsibility to check QThread.isInterruptionRequested() frequently enough in the code after each block of work is done.

    Of course you can develop your own methods for aborting the work, possibly guarded by mutexes... Nevertheless you must check it periodically. There is no way around it.

    def minimize(self, something): # a method of Worker class
      for i in range(1000000):
        if i % 1000 == 0: # this is just to represent a block of work
          if self.thread().isInterruptionRequested():
            return
        self.minimize_next_part(something)
      self.finished.emit()
    

    The stopper function should then be:

    def stop_solving(self):
      self.thread.requestInterruption() # this will unblock the event loop in just a moment
      self.thread.quit() # this ends event loop and will emit thread's finished signal
      # self.thread.wait() # you do not need this probbaly, it depends...
    

    (I am sorry for potential syntax errors, I am C++ programmer, not Pythonista)

    I know this looks stupid, but really there is no other miraculous mechanism for interrupting threads. You simply need periodical polling.

    PS: instead of

    self.worker.finished.connect(self.worker.deleteLater)
    

    you should use

    self.thread.finished.connect(self.worker.deleteLater)
    

    otherwise the worker will not be deleted if the thread gets interrupted because then Worker.finished signal gets never called.