Use a Qrunnable to handle the execution of other QRunnables

Heads up! You've already completed this tutorial.

cesant210858 | 2021-04-19 09:09:18 UTC | #1

I am struggling (a lot) with a problem: I am launching a process that requires some computational resources and sometimes takes a lot of time. Since I cannot kill a running QRunnable with a button, I would like to use a QRunnable that launches no more than 6 other processes in parallel. Here is an example of what I thought:

python
from PyQt5  import QtWidgets, QtCore
from time   import sleep
from sys    import argv, exc_info, exit

import traceback

threadpool                       = QtCore.QThreadPool()
max_paralell_processes = 6
class MainWindow(QtWidgets.QMainWindow):
    stopsignal = QtCore.pyqtSignal()
    def __init__(self):
        super().__init__()
        self.trainers  = {}
        self.ntrainers = 10

        self.startButton = QtWidgets .QPushButton('Start', self)
        self.startButton.resize(self.startButton.sizeHint())
        self.startButton.move(50, 50)

        self.stopButton = QtWidgets.QPushButton('Stop', self)
        self.stopButton.resize(self.stopButton.sizeHint())
        self.stopButton.move(150, 50)

        self.setGeometry(300, 300, 300, 200)

        self.startButton.clicked.connect(self.create_trainers)
        self.stopButton.clicked.connect(self.stop)

        self.progressBar = QtWidgets.QProgressBar(self)
        self.progressBar.setGeometry(50, 20, 250, 20)
        self.count       = 0
        self.progressBar.setValue(self.count)


    def create_trainers(self):
        global threadpool

        for m in range(self.ntrainers):
            trainer = Trainer(self.the_long_process, m)
            trainer.progress.connect( self.progress_bar)
            trainer.finished.connect( self.finished_process)

            self.trainers[m] = trainer

        launcher      = LaunchWorkers(self.trainers, range(self.ntrainers))
        launcher.finished.connect(self.__all_processes_finished)
        self.stopsignal.connect(launcher.stop)
        threadpool.start(launcher)

    def the_long_process(self, m):
        print("In process {}".format(m))
        sleep(2)

    def progess_bar(self):
        self.count += 1
        self.progressBar.setValue(self.count/len(self.trainers)*100)

    def finished_process(self):
        pass

    def stop(self):
        self.stopsignal.emit()

class Trainer(QtCore.QRunnable):
    finished     = QtCore.pyqtSignal(str)
    error        = QtCore.pyqtSignal(tuple)
    progress     = QtCore.pyqtSignal()

    def __init__(self, the_fn, m):
        super(Trainer, self).__init__()
        self.the_fn = the_fn
        self.m      = m
        self.terminated  = False

    @QtCore.pyqtSlot()
    def run(self):
        try:
            self.the_fn ( self.m )
        except:
            traceback.print_exc()
            exctype, value = exc_info()[:2]
            self.error.emit((exctype, value, traceback.format_exc()))
            self.terminated = True
        else:
            self.progress.emit()
        finally:
            self.terminated = True
            self.finished.emit( self.m)


class LaunchWorkers(QtCore.QRunnable):
    finished     = QtCore.pyqtSignal()
    error        = QtCore.pyqtSignal(tuple)

    def __init__(self, trainers, some_list ):
        super().__init__()
        self.trainers  = trainers
        self.some_list = some_list
        self.stop      = False

    def stop(self):
        self.stop = True

    @QtCore.pyqtSlot()
    def run(self):
        global threadpool

        finished_threads = 0
        try:
            for m in self.some_list :
                if self.stop:
                    break
                while finished_threads <= max_paralell_processes :
                    sleep(10)
                    finished_threads = 0
                    for t in self.trainers:
                        if self.trainers[t].terminated:
                            finished_threads += 1
                threadpool.start( self.trainers[m] )
        except:
            traceback.print_exc()
            exctype, value = exc_info()[:2]
            self.error.emit((exctype, value, traceback.format_exc()))
        finally:
            self.finished.emit(  )

def main():
    app = QtWidgets.QApplication(argv)
    ex = MainWindow()
    ex.show()
    exit(app.exec_())


if __name__ == '__main__':
    main()

Doing so, I get a TypeError ModelTrainer cannot be converted to PyQt5.QtCore.QObject in this context.


Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

Use a Qrunnable to handle the execution of other QRunnables was written by Martin Fitzpatrick .

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular https://www.martinfitzpatrick.com/browse/books/ on the subject.