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

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.