pythonpyqtpyqt5threadpoolqtimer

PyQt5 Qtimer don't start


i'm stucked on a Qtimer that don't start, so the number that i need to update on the GUI is never showed.

i don't want to use a while loop inside the code because i need to change some values in real time without any problem.

i really don't understand why it don't start... or better, it start, but don't run the function update_label.

any suggestion?

Thanks a lot for your time!

here is my code:

class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setObjectName("MainWindow")
        self.resize(1300, 768)
        self.setMinimumSize(1300,768)
        _translate = QtCore.QCoreApplication.translate
        self.setWindowTitle(_translate("Bot", "Bot"))
        
        self.number = QtWidgets.QLabel(self)
        self.number .setGeometry(QtCore.QRect(1100, 10, 300, 20))
        self.number .setObjectName("number")
        
        
        self.show()
        
        
    
        
        self.threadpool = QThreadPool()
        self.updater = Updater()
        self.threadpool.start(self.updater)
        
        self.updater.update_progress.latest_number.connect(self.update_number)

    @pyqtSlot(str)
    def update_number(self, val):
        x = str(val)
        self.number.setText("Current block: " + x)  
        
  
    
class Updater(QRunnable):
    def __init__(self):
        super(Updater, self).__init__()
        self.update_progress = WorkerSignal()
        
        print("started")
        
        
    def run(self):
        self.timer = QTimer()
        self.timer.setInterval(2000)
        self.timer.timeout.connect(self.update_label)
        self.timer.start()
            
        
        
    def update_label(self):
        
        provider = configfile.provider
        number = str(nmbr.latest_numer(provider))
        self.update_progress.latest_number.emit(number)
        print("update label started")

Solution

  • After tyring to understand your script, I reached the conclusion that QRunnable is automatically released (or stopped) once Updater.run(self) reaches its end.

    But this is supposed to happen, because that's why you should use a QThreadPool in the first place. It is supposed to recycle expired threads in the background of the application.

    So, as you're running a QTimer in the thread, QThreadPool thinks that Updater thread is dead, while in reality, the timer is still running on the background of a background thread. So it does its job and releases Updater from the Thread pool.

    What you must do in order to keep both the Timer and Updater alive is to manager the Updater thread's lifecycle yourself.

    So instead of using QThreadPool and QRunnable, you must go one level lower in terms of abstraction, and use the QThread and QObject themselves in order to control when the thread is going to be stopped.

    from PySide2 import QtWidgets, QtCore
    from PySide2.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
    from PySide2.QtCore import Qt, QThreadPool, QRunnable, Slot, Signal, QObject, QTimer, QThread
    
    class WorkerSignal(QObject):
        # Changed pyqtSignal to Signal
        latest_number = Signal(str)
    
    class Worker(QObject):
        def __init__(self):
            QObject.__init__(self)
            self.update_progress = WorkerSignal()
    
        def update_label(self):
            print("update label started")
            try:
                provider = configfile.provider
                number = str(nmbr.latest_numer(provider))
                self.update_progress.latest_number.emit(number)
            except:
                print("Error, but just for testing...")
                self.update_progress.latest_number.emit("0")
    
        def main(self):
            print('Init worker')
            self.timer = QTimer()
            self.timer.setInterval(2000)
            self.timer.timeout.connect(self.update_label)
            self.thread().finished.connect(self.timer.stop)
            self.timer.start()
    
    class Updater(QThread):
        def __init__(self):
            QThread.__init__(self)
    
            self.worker = Worker()
    
            # Move worker to another thread, to execute in parallel
            self.worker.moveToThread(self)
    
            # Set which method from Worker that should excute on the other thread. 
            # In this case: Worker.main
            self.started.connect(self.worker.main)
    
    class MainWindow(QMainWindow):
        def __init__(self, *args, **kwargs):
            QMainWindow.__init__(self, *args, **kwargs)
            
            self.setObjectName("MainWindow")
            self.resize(1300, 768)
            self.setMinimumSize(1300,768)
            _translate = QtCore.QCoreApplication.translate
            self.setWindowTitle(_translate("SniperBot", "SniperBot"))
            
            self.number = QtWidgets.QLabel(self)
            self.number.setGeometry(QtCore.QRect(1100, 10, 300, 20))
            self.number.setObjectName("number")
    
            # I did this to see the label, as it was not attached to any
            # widget's layout on your script.
            widget = QWidget()
            layout = QVBoxLayout()
            widget.setLayout(layout)
            layout.addWidget(self.number)
            self.setCentralWidget(widget)
            self.scene = widget
            
            self.show()
            
            # Create the Updater thread.
            self.updater = Updater()
    
            # Connect the Worker's signal to self.update_number
            self.updater.worker.update_progress.latest_number.connect(self.update_number)
    
            # Start the thread
            self.updater.start()
    
        def closeEvent(self, evt):
            # Before exiting from the application, remember, we control now
            # the Updater Thread's lifecycle. So it's our job to stop the thread
            # and wait for it to finish properly.
            self.updater.quit()
            self.updater.wait()
    
            # Accept the event. Exit the application.
            evt.accept()
    
        # Changed pyqtSlot to Slot
        @Slot(str)
        def update_number(self, val):
            x = str(val)
            print("UPDATE NUMBER: ", x)
            self.number.setText("Current block: " + x)  
    
    if __name__ == '__main__':
        app = QApplication()
        win = MainWindow()
        win.show()
        app.exec_()
    

    What you can do now is follow this pattern and start implementing from here. There are many tutorials that use the moveToThread method. Just be careful to not let the thread or any object be recycled by the python's garbage collector, in order to avoid many other problems.

    The script is working, however I use PySide2 instead of PyQt5. So some variable and modules names may change, so just rename them when you try to run the script on your end.


    There were a few errors on Worker.update_label(self), which is just a copied script from the old Updater.update_label(self). I don't know what are the variables: configfile or nmbr as they are not initialized on your post. So I made a try-except block to handle the error and make the script work just for testing.