Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QThread: Destroyed while thread is still running - PyQT5 Issues
Forum Updated to NodeBB v4.3 + New Features

QThread: Destroyed while thread is still running - PyQT5 Issues

Scheduled Pinned Locked Moved Unsolved General and Desktop
2 Posts 2 Posters 7.2k Views
  • 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.
  • Z Offline
    Z Offline
    ZSSchulze
    wrote on last edited by
    #1

    I am trying to figure out why this code crashes if I try to run the threads for a second time once they are completed.

    The first time I click "Start 5 Threads" It runs just fine and finishes. But if I click it again. The entire program crashes and I get the QThread: Destroyed while thread is still running Error

    import time
    import sys
    
    from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
    from PyQt5.QtWidgets import QApplication, QPushButton, QTextEdit, QVBoxLayout, QWidget
    
    
    def trap_exc_during_debug(*args):
        # when app raises uncaught exception, print info
        print(args)
    
    
    # install exception hook: without this, uncaught exception would cause application to exit
    sys.excepthook = trap_exc_during_debug
    
    
    class Worker(QObject):
        """
        Must derive from QObject in order to emit signals, connect slots to other signals, and operate in a QThread.
        """
    
        sig_step = pyqtSignal(int, str)  # worker id, step description: emitted every step through work() loop
        sig_done = pyqtSignal(int)  # worker id: emitted at end of work()
        sig_msg = pyqtSignal(str)  # message to be shown to user
    
        def __init__(self, id: int):
            super().__init__()
            self.__id = id
            self.__abort = False
    
        @pyqtSlot()
        def work(self):
            """
            Pretend this worker method does work that takes a long time. During this time, the thread's
            event loop is blocked, except if the application's processEvents() is called: this gives every
            thread (incl. main) a chance to process events, which in this sample means processing signals
            received from GUI (such as abort).
            """
            thread_name = QThread.currentThread().objectName()
            thread_id = int(QThread.currentThreadId())  # cast to int() is necessary
            self.sig_msg.emit('Running worker #{} from thread "{}" (#{})'.format(self.__id, thread_name, thread_id))
    
            for step in range(100):
                time.sleep(0.1)
                self.sig_step.emit(self.__id, 'step ' + str(step))
    
                # check if we need to abort the loop; need to process events to receive signals;
                app.processEvents()  # this could cause change to self.__abort
                if self.__abort:
                    # note that "step" value will not necessarily be same for every thread
                    self.sig_msg.emit('Worker #{} aborting work at step {}'.format(self.__id, step))
                    break
    
            self.sig_done.emit(self.__id)
    
        def abort(self):
            self.sig_msg.emit('Worker #{} notified to abort'.format(self.__id))
            self.__abort = True
    
    
    class MyWidget(QWidget):
        NUM_THREADS = 5
    
        # sig_start = pyqtSignal()  # needed only due to PyCharm debugger bug (!)
        sig_abort_workers = pyqtSignal()
    
        def __init__(self):
            super().__init__()
    
            self.setWindowTitle("Thread Example")
            form_layout = QVBoxLayout()
            self.setLayout(form_layout)
            self.resize(400, 800)
    
            self.button_start_threads = QPushButton()
            self.button_start_threads.clicked.connect(self.start_threads)
            self.button_start_threads.setText("Start {} threads".format(self.NUM_THREADS))
            form_layout.addWidget(self.button_start_threads)
    
            self.button_stop_threads = QPushButton()
            self.button_stop_threads.clicked.connect(self.abort_workers)
            self.button_stop_threads.setText("Stop threads")
            self.button_stop_threads.setDisabled(True)
            form_layout.addWidget(self.button_stop_threads)
    
            self.log = QTextEdit()
            form_layout.addWidget(self.log)
    
            self.progress = QTextEdit()
            form_layout.addWidget(self.progress)
    
            QThread.currentThread().setObjectName('main')  # threads can be named, useful for log output
            self.__workers_done = None
            self.__threads = None
    
        def start_threads(self):
            self.log.append('starting {} threads'.format(self.NUM_THREADS))
            self.button_start_threads.setDisabled(True)
            self.button_stop_threads.setEnabled(True)
    
            self.__workers_done = 0
            self.__threads = []
            for idx in range(self.NUM_THREADS):
                worker = Worker(idx)
                thread = QThread()
                thread.setObjectName('thread_' + str(idx))
                self.__threads.append((thread, worker))  # need to store worker too otherwise will be gc'd
                worker.moveToThread(thread)
    
                # get progress messages from worker:
                worker.sig_step.connect(self.on_worker_step)
                worker.sig_done.connect(self.on_worker_done)
                worker.sig_msg.connect(self.log.append)
    
                # control worker:
                self.sig_abort_workers.connect(worker.abort)
    
                # get read to start worker:
                # self.sig_start.connect(worker.work)  # needed due to PyCharm debugger bug (!); comment out next line
                thread.started.connect(worker.work)
                thread.start()  # this will emit 'started' and start thread's event loop
    
            # self.sig_start.emit()  # needed due to PyCharm debugger bug (!)
    
        @pyqtSlot(int, str)
        def on_worker_step(self, worker_id: int, data: str):
            self.log.append('Worker #{}: {}'.format(worker_id, data))
            self.progress.append('{}: {}'.format(worker_id, data))
    
        @pyqtSlot(int)
        def on_worker_done(self, worker_id):
            self.log.append('worker #{} done'.format(worker_id))
            self.progress.append('-- Worker {} DONE'.format(worker_id))
            self.__workers_done += 1
            if self.__workers_done == self.NUM_THREADS:
                self.log.append('No more workers active')
                self.button_start_threads.setEnabled(True)
                self.button_stop_threads.setDisabled(True)
                # self.__threads = None
    
        @pyqtSlot()
        def abort_workers(self):
            self.sig_abort_workers.emit()
            self.log.append('Asking each worker to abort')
            for thread, worker in self.__threads:  # note nice unpacking by Python, avoids indexing
                thread.quit()  # this will quit **as soon as thread event loop unblocks**
                thread.wait()  # <- so you need to wait for it to *actually* quit
    
            # even though threads have exited, there may still be messages on the main thread's
            # queue (messages that threads emitted before the abort):
            self.log.append('All threads exited')
    
    
    if __name__ == "__main__":
        app = QApplication([])
    
        form = MyWidget()
        form.show()
    
        sys.exit(app.exec_())
    
    
    jsulmJ 1 Reply Last reply
    0
    • Z ZSSchulze

      I am trying to figure out why this code crashes if I try to run the threads for a second time once they are completed.

      The first time I click "Start 5 Threads" It runs just fine and finishes. But if I click it again. The entire program crashes and I get the QThread: Destroyed while thread is still running Error

      import time
      import sys
      
      from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
      from PyQt5.QtWidgets import QApplication, QPushButton, QTextEdit, QVBoxLayout, QWidget
      
      
      def trap_exc_during_debug(*args):
          # when app raises uncaught exception, print info
          print(args)
      
      
      # install exception hook: without this, uncaught exception would cause application to exit
      sys.excepthook = trap_exc_during_debug
      
      
      class Worker(QObject):
          """
          Must derive from QObject in order to emit signals, connect slots to other signals, and operate in a QThread.
          """
      
          sig_step = pyqtSignal(int, str)  # worker id, step description: emitted every step through work() loop
          sig_done = pyqtSignal(int)  # worker id: emitted at end of work()
          sig_msg = pyqtSignal(str)  # message to be shown to user
      
          def __init__(self, id: int):
              super().__init__()
              self.__id = id
              self.__abort = False
      
          @pyqtSlot()
          def work(self):
              """
              Pretend this worker method does work that takes a long time. During this time, the thread's
              event loop is blocked, except if the application's processEvents() is called: this gives every
              thread (incl. main) a chance to process events, which in this sample means processing signals
              received from GUI (such as abort).
              """
              thread_name = QThread.currentThread().objectName()
              thread_id = int(QThread.currentThreadId())  # cast to int() is necessary
              self.sig_msg.emit('Running worker #{} from thread "{}" (#{})'.format(self.__id, thread_name, thread_id))
      
              for step in range(100):
                  time.sleep(0.1)
                  self.sig_step.emit(self.__id, 'step ' + str(step))
      
                  # check if we need to abort the loop; need to process events to receive signals;
                  app.processEvents()  # this could cause change to self.__abort
                  if self.__abort:
                      # note that "step" value will not necessarily be same for every thread
                      self.sig_msg.emit('Worker #{} aborting work at step {}'.format(self.__id, step))
                      break
      
              self.sig_done.emit(self.__id)
      
          def abort(self):
              self.sig_msg.emit('Worker #{} notified to abort'.format(self.__id))
              self.__abort = True
      
      
      class MyWidget(QWidget):
          NUM_THREADS = 5
      
          # sig_start = pyqtSignal()  # needed only due to PyCharm debugger bug (!)
          sig_abort_workers = pyqtSignal()
      
          def __init__(self):
              super().__init__()
      
              self.setWindowTitle("Thread Example")
              form_layout = QVBoxLayout()
              self.setLayout(form_layout)
              self.resize(400, 800)
      
              self.button_start_threads = QPushButton()
              self.button_start_threads.clicked.connect(self.start_threads)
              self.button_start_threads.setText("Start {} threads".format(self.NUM_THREADS))
              form_layout.addWidget(self.button_start_threads)
      
              self.button_stop_threads = QPushButton()
              self.button_stop_threads.clicked.connect(self.abort_workers)
              self.button_stop_threads.setText("Stop threads")
              self.button_stop_threads.setDisabled(True)
              form_layout.addWidget(self.button_stop_threads)
      
              self.log = QTextEdit()
              form_layout.addWidget(self.log)
      
              self.progress = QTextEdit()
              form_layout.addWidget(self.progress)
      
              QThread.currentThread().setObjectName('main')  # threads can be named, useful for log output
              self.__workers_done = None
              self.__threads = None
      
          def start_threads(self):
              self.log.append('starting {} threads'.format(self.NUM_THREADS))
              self.button_start_threads.setDisabled(True)
              self.button_stop_threads.setEnabled(True)
      
              self.__workers_done = 0
              self.__threads = []
              for idx in range(self.NUM_THREADS):
                  worker = Worker(idx)
                  thread = QThread()
                  thread.setObjectName('thread_' + str(idx))
                  self.__threads.append((thread, worker))  # need to store worker too otherwise will be gc'd
                  worker.moveToThread(thread)
      
                  # get progress messages from worker:
                  worker.sig_step.connect(self.on_worker_step)
                  worker.sig_done.connect(self.on_worker_done)
                  worker.sig_msg.connect(self.log.append)
      
                  # control worker:
                  self.sig_abort_workers.connect(worker.abort)
      
                  # get read to start worker:
                  # self.sig_start.connect(worker.work)  # needed due to PyCharm debugger bug (!); comment out next line
                  thread.started.connect(worker.work)
                  thread.start()  # this will emit 'started' and start thread's event loop
      
              # self.sig_start.emit()  # needed due to PyCharm debugger bug (!)
      
          @pyqtSlot(int, str)
          def on_worker_step(self, worker_id: int, data: str):
              self.log.append('Worker #{}: {}'.format(worker_id, data))
              self.progress.append('{}: {}'.format(worker_id, data))
      
          @pyqtSlot(int)
          def on_worker_done(self, worker_id):
              self.log.append('worker #{} done'.format(worker_id))
              self.progress.append('-- Worker {} DONE'.format(worker_id))
              self.__workers_done += 1
              if self.__workers_done == self.NUM_THREADS:
                  self.log.append('No more workers active')
                  self.button_start_threads.setEnabled(True)
                  self.button_stop_threads.setDisabled(True)
                  # self.__threads = None
      
          @pyqtSlot()
          def abort_workers(self):
              self.sig_abort_workers.emit()
              self.log.append('Asking each worker to abort')
              for thread, worker in self.__threads:  # note nice unpacking by Python, avoids indexing
                  thread.quit()  # this will quit **as soon as thread event loop unblocks**
                  thread.wait()  # <- so you need to wait for it to *actually* quit
      
              # even though threads have exited, there may still be messages on the main thread's
              # queue (messages that threads emitted before the abort):
              self.log.append('All threads exited')
      
      
      if __name__ == "__main__":
          app = QApplication([])
      
          form = MyWidget()
          form.show()
      
          sys.exit(app.exec_())
      
      
      jsulmJ Offline
      jsulmJ Offline
      jsulm
      Lifetime Qt Champion
      wrote on last edited by
      #2

      @ZSSchulze said in QThread: Destroyed while thread is still running - PyQT5 Issues:

      self.__threads = []

      you call this without stopping/terminating the threads - my guess is that garbage collector deletes the objects (thread + worker). In start_threads you should first make sure all active threads are finished or stop them.

      https://forum.qt.io/topic/113070/qt-code-of-conduct

      1 Reply Last reply
      1

      • Login

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