Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Stop thread that's running a while loop
Forum Updated to NodeBB v4.3 + New Features

Stop thread that's running a while loop

Scheduled Pinned Locked Moved Unsolved Qt for Python
5 Posts 3 Posters 1.3k Views 3 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    DieterV
    wrote on 3 Sept 2023, 14:19 last edited by
    #1

    I've tried to avoid needing multithreading as long as possible but now I need something that is running while not blocking the main window. The more I read about multithreading, the more I got scared of it ("a million ways to do it wrong if you don't absolutely know everything about all of it" seems a common comment).

    So I decided to come up with a minimal example for myself to see how to do it. I've used two books and a couple of tutorials.

    What I got works, but now I'd like to break a while loop running in a thread.

    One thing to note: I've read multiple times that it is better to subclass your worker from QObject than from QThread, so that is what I did.

    Yes, this might be considered cargo cult programming. I'm trying to learn and can't understand everything right away.

    import datetime
    import random
    import sys
    import time
    from PyQt5 import QtWidgets as qtw
    from PyQt5 import QtGui as qtg
    from PyQt5 import QtCore as qtc
    
    
    #########################
    # Subclass from QObject #
    #########################
    class WorkerClass(qtc.QObject):
        ###############################################
        # Define signals at class level, not instance #
        ###############################################
        loop_start_signal = qtc.pyqtSignal(int)
        loop_done_signal = qtc.pyqtSignal(str)
    
        def __init__(self):
            super().__init__()
    
        ################################
        # Define slots using decorator #
        ################################
        @qtc.pyqtSlot()
        def loop_in_worker(self):
            self.running: bool = True
            # while self.running:  # nope.
            while not self.isInterruptionRequested:  # Not a member of QObject
                sleep_int = random.randrange(1, 5)
                self.loop_start_signal.emit(sleep_int)
                time.sleep(sleep_int)
    
                ########################################################
                # Emit signals that are linked to slots in main window #
                ########################################################
                self.loop_done_signal.emit(
                    datetime.datetime.now(datetime.timezone.utc).strftime('%H:%M:%S'))
    
        @qtc.pyqtSlot()
        def stop_looping(self):
            self.running = False
    
    
    class MainWindow(qtw.QMainWindow):
    
        start_looping = qtc.pyqtSignal()
        stop_thread_signal = qtc.pyqtSignal()
    
        def __init__(self):
            """MainWindow constructor."""
            super().__init__()
            ################
            # Main UI code #
            ################
            layout = qtw.QFormLayout()
            self.go_button = qtw.QPushButton('Start Hashing')
            self.go_button.setCheckable(True)
            layout.addRow('Button:', self.go_button)
            self.next_sleep_length = qtw.QLabel()
            layout.addRow('Next sleep length:', self.next_sleep_length)
    
            main_widget = qtw.QWidget()
            main_widget.setLayout(layout)
    
            self.setCentralWidget(main_widget)
    
            #######################################
            # Create a worker object and a thread #
            #######################################
            self.worker = WorkerClass()
            self.worker_thread = qtc.QThread()
    
            ################################################
            # Connect local signals to slots in the Worker #
            # BEFORE moving worker to thread               #
            ################################################
            # self.go_button.clicked.connect(self.worker.loop_in_worker)
            self.go_button.clicked.connect(self.handle_button_clicked)
            self.start_looping.connect(self.worker.loop_in_worker)
            self.stop_thread_signal.connect(self.worker.stop_looping)
    
            ##############################################
            # Connect signals from Worker to local slots #
            # BEFORE moving worker to thread             #
            ##############################################
            self.worker.loop_done_signal.connect(self.receive_done_from_worker)
            self.worker.loop_start_signal.connect(self.receive_sleep_length_from_worker)
    
            ########################################################
            # Assign the worker to the thread and start the thread #
            ########################################################
            self.worker.moveToThread(self.worker_thread)
            self.show()
    
        ################################
        # Define slots using decorator #
        ################################
        @qtc.pyqtSlot(bool)
        def handle_button_clicked(self, button_down: bool):
            print(f'Button down: {button_down}')
            if button_down:
                self.worker_thread.start()
                self.start_looping.emit()
            else:
                ###################################
                # This is where I'm stuck.        #
                # How do I stop the while loop or #
                # forcibly interrupt the thread?  #
                ###################################
                self.worker_thread.requestInterruption(
                )  # My class isn't a subclass from QThread so doesn't know that
                # self.stop_thread_signal.emit()  # doesn't get handled by the thread event loop
                # self.worker_thread.quit()  # Doesn't seem to do anything
                # # self.worker_thread.wait() # keeps waiting because while loop isn't interrupted
    
        @qtc.pyqtSlot(int)
        def receive_sleep_length_from_worker(self, length: int):
            self.next_sleep_length.setText(f'{length} seconds')
    
        @qtc.pyqtSlot(str)
        def receive_done_from_worker(self, donetime: str):
            print(f'Done: {donetime}')
    
    
    if __name__ == '__main__':
        app = qtw.QApplication(sys.argv)
        mw = MainWindow()
        sys.exit(app.exec())
    
    

    I've tried several things:

    • using a self.running attribute but the thread is obviously stuck in the while loop so the event handler doesn't get to the slot I call.
    • requestInterruption() but my worker isn't subclassed from QThread
    • A number of things I've since learned aren't a good idea.

    What is the "correct" way to terminate this thread (preferably in a way so that I can start it again).

    I hope my example code is clear. It's just a button that starts a thread that contains a while loop that executes sleep() or random lengths and "reports back" to the main window. Once again, this is not meant to do anything useful besides showing me how to work with threads. I'd like to avoid as much complication as reasonable.

    1 Reply Last reply
    0
    • S Offline
      S Offline
      SGaist
      Lifetime Qt Champion
      wrote on 3 Sept 2023, 14:55 last edited by
      #2

      Hi,

      The first question to answer is: which type of threading technique do you need ? Worker object or custom thread ? Both can be valid and thus determining which one will yield different result in how things can be stopped.

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      D 1 Reply Last reply 3 Sept 2023, 18:14
      0
      • S SGaist
        3 Sept 2023, 14:55

        Hi,

        The first question to answer is: which type of threading technique do you need ? Worker object or custom thread ? Both can be valid and thus determining which one will yield different result in how things can be stopped.

        D Offline
        D Offline
        DieterV
        wrote on 3 Sept 2023, 18:14 last edited by
        #3

        @SGaist

        Thank you for your reply. I tried making my example code as simple as possible to make it clear (for this forum as well as for myself), so the reason for requiring threading is indeed not clear.

        I have not seen the question you ask come up before in what I read so far. Maybe it is obvious for most people, but I don't know what you mean exactly I'm afraid...

        The bigger picture is this: I've written a piece of software as a favor for a company that I sometimes work for. I had no decent experience with PyQt before. Obviously, what started as "we just need to do x" became a much bigger piece of software. It's been a very good learning experience. However, when the software is used (checking in guests at conferences), having it fail while there's queues of people waiting to get is not a pleasant foresight. So I err on the side of caution whenever I implement something new.

        Now I need to add syncing in real time. Different "check-in stations" where hostesses help people to scan their access codes need to wirelessly report in real time to a central spot where the client can see how much people are in, who, ...
        I've decided to do this using the Reticulum network stack (as it can be extended over LoRa and VHF amongst other things). So I need to write a handler that takes care of the connection, sends data when a new check-in is performed, responds when the link goes down, ...

        This seemed not something I wanted to do in the main event loop as it might take a while to connect.

        Does that make sense?

        Thanks again.

        S 1 Reply Last reply 3 Sept 2023, 19:20
        0
        • D DieterV
          3 Sept 2023, 18:14

          @SGaist

          Thank you for your reply. I tried making my example code as simple as possible to make it clear (for this forum as well as for myself), so the reason for requiring threading is indeed not clear.

          I have not seen the question you ask come up before in what I read so far. Maybe it is obvious for most people, but I don't know what you mean exactly I'm afraid...

          The bigger picture is this: I've written a piece of software as a favor for a company that I sometimes work for. I had no decent experience with PyQt before. Obviously, what started as "we just need to do x" became a much bigger piece of software. It's been a very good learning experience. However, when the software is used (checking in guests at conferences), having it fail while there's queues of people waiting to get is not a pleasant foresight. So I err on the side of caution whenever I implement something new.

          Now I need to add syncing in real time. Different "check-in stations" where hostesses help people to scan their access codes need to wirelessly report in real time to a central spot where the client can see how much people are in, who, ...
          I've decided to do this using the Reticulum network stack (as it can be extended over LoRa and VHF amongst other things). So I need to write a handler that takes care of the connection, sends data when a new check-in is performed, responds when the link goes down, ...

          This seemed not something I wanted to do in the main event loop as it might take a while to connect.

          Does that make sense?

          Thanks again.

          S Offline
          S Offline
          SGaist
          Lifetime Qt Champion
          wrote on 3 Sept 2023, 19:20 last edited by
          #4

          I would start by first getting this part running and then benchmark it.

          After that, consider threading. Depending on what you need to do, using a message queue might be enough (for example if just sending updates from the client to a central server).

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          1 Reply Last reply
          0
          • S Offline
            S Offline
            SimonSchroeder
            wrote on 5 Sept 2023, 06:28 last edited by
            #5

            while self.running: would at least work inside C++. Python has additional quirks due to the Global Interpreter Lock. I don't use Python and thus don't know the details.

            As you have figured out isInterruptionRequested is a method of QThread. QThread also has a class method (in C++, don't know how this translates to Python) currentThread() to get the thread of the context of your code. So, in C++ it would look like this: while(!QThread::currentThread().isInterruptionRequested()) ....

            1 Reply Last reply
            1

            5/5

            5 Sept 2023, 06:28

            • Login

            • Login or register to search.
            5 out of 5
            • First post
              5/5
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved