0

I am creating a GUI with python (v3.9) and PyQt6 for a program I developed. At the moment I am trying to find a way in which the moment a user clicks a button, a for loop starts. Within this for loop the user gets prompted for an input. Once the user has provided that input, a second function using that input is run, and once that function is finished a new loop within the original function starts.

Searching for solutions I found the multithreading library of PyQt and started implementing a possible solution for my problem.

Everything works as expected if the number of loops is only one (uncommented part of the function for_loop), but otherwise it crashes and shows an error QThread: Destroyed while thread is still running. Is this the right way to approach this problem? Any suggestions are highly appreciated.

I am new to multithreading so the code I have been using is a modified example of one I found online and I have been modifying it as I go.

A simplified version of what I have so far is next:

from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6 import QtWidgets
import time

# Step 1: Create a worker class
class MyWorker(QObject):

    wait_for_input = pyqtSignal()
    done = pyqtSignal()

    @pyqtSlot()
    def firstWork(self):
        print('Waiting for user input')
        self.wait_for_input.emit()

    @pyqtSlot()
    def secondWork(self):
        print('doing second work')
        time.sleep(2)
        print('second work done')
        self.done.emit()


class Window(QtWidgets.QWidget):
    def __init__(self, parent = None):
        super(Window, self).__init__()

        self.initUi()

    def initUi(self):
        layout = QtWidgets.QVBoxLayout()
        self.start_button = QtWidgets.QPushButton('Start')
        self.start_button.setEnabled(True)
        layout.addWidget(self.start_button)
        self.textBrowser = QtWidgets.QTextBrowser()
        layout.addWidget(self.textBrowser)
        self.lineEdit = QtWidgets.QLineEdit()
        self.lineEdit.setEnabled(False)
        layout.addWidget(self.lineEdit)
        self.button = QtWidgets.QPushButton('Set')
        self.button.setEnabled(False)
        layout.addWidget(self.button)
        self.label = QtWidgets.QLabel()
        self.label.setGeometry(QRect(20, 20, 211, 16))
        self.label.setText('Loop No:')
        layout.addWidget(self.label)
        self.loop_label = QtWidgets.QLabel()
        self.loop_label.setGeometry(QRect(20, 20, 211, 16))
        self.loop_label.setText('0')
        layout.addWidget(self.loop_label)
        
        self.setLayout(layout)
        self.show()

        self.start_button.clicked.connect(lambda: self.for_loop())

    @pyqtSlot()
    def enable_user_input(self):
        self.lineEdit.setEnabled(True)
        self.button.setEnabled(True)

    @pyqtSlot()    
    def done(self):
        self.textBrowser.append("DONE")

    def for_loop(self): 
        n = 1
        print('Loop No.', n)
        self.loop_label.setText(str(n))
        self.textBrowser.append("Running loop No."+ str(n))
        self.get_user_input()
        
        # for n in range(1,4,1): 
        #     print('Loop No.', n)
        #     self.loop_label.setText(str(n))
        #     self.textBrowser.append("Running loop No."+ str(n))
        #     self.get_user_input()

    def get_user_input(self):
        # Step 2: Create a QThread object
        self.thread = QThread()
        # Step 3: Create a worker object
        self.worker = MyWorker()
        # Step 4: Move worker to the thread
        self.worker.moveToThread(self.thread)
        self.textBrowser.append("Provide a list of numbers from 10 to 100 separated by commas:")
        # Step 5: Connect signals and slots
        # - Start the thread
        self.thread.started.connect(self.worker.firstWork)

        # - Thread waiting for user input
        self.worker.wait_for_input.connect(self.enable_user_input)
        # - User has provided input
        self.button.clicked.connect(self.user_input_provided)

        # - End the thread
        #   > Print DONE
        self.worker.done.connect(self.done)
        #   > Quit the thread
        self.worker.done.connect(self.thread.quit)
        #   > Delete the worker
        self.worker.done.connect(self.worker.deleteLater)
        #   > Delete the Thread
        self.thread.finished.connect(self.thread.deleteLater)

        # Step 6: Start the thread
        self.thread.start()    
    
    def user_input_provided(self):
        user_input = self.lineEdit.text()
        print('user_input:', user_input)
        self.textBrowser.append(user_input)
        self.lineEdit.clear()
        self.lineEdit.setEnabled(False)
        self.button.setEnabled(False)
        self.worker.secondWork()

if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    w = Window()
    app.exec()
2
  • There are too many problems with your script for it to be worth fixing. You need to completely restructure the code. At the moment, the logic is backwards. You need to get rid of the for-loop and avoid calling the worker methods directly (since they will be executed withing the main thread). The basic steps should be: (1) get user input; (2) create new worker, passing input to it via __init__; (3) start thread; (4) in a slot connected to the worker's done signal, quit the thread, clean up, and then go to (1). Commented Sep 29, 2023 at 11:02
  • PS: the solutions to the linked questions above don't use threads (although they could), but the logic of the code is essentially the same as outlined in my previous comment. The point is that the worker (in this case a web-page) operates asynchronously, whilst the client code waits for a response and then restarts the cycle when it's received. Commented Sep 29, 2023 at 11:17

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.