2

I want to run pyqt5 QThreads in parallel but my code seems to run in sequence can someone tell me how to run QThreads in parallel?

The output from my code: I expected that it runs in parallel, not in sequence.

Multithreading with maximum 4 threads
You pressed the Test button
Job 1
Job 2
Job 3
Job 4
Done.
THREAD COMPLETE!

Code:

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

import time
import traceback, sys

uart_result = ['1','2', '3', '4', '5', '6']

#Running all these methods in parallel
@pyqtSlot()
def run1():
    print("Job 1")
    return uart_result

@pyqtSlot()
def run2():
    print("Job 2")
    return uart_result

@pyqtSlot()
def run3():
    print("Job 3")
    return uart_result

@pyqtSlot()
def run4():
    print("Job 4")
    return uart_result

class WorkerSignals(QObject):
    finished = pyqtSignal()
    error = pyqtSignal(tuple)
    result = pyqtSignal(object)
    list = pyqtSignal(list)
    progress = pyqtSignal(int)


class Worker(QRunnable):

    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        # Add the callback to our kwargs
        self.kwargs['progress_callback'] = self.signals.progress

    @pyqtSlot()
    def run(self):
        '''
        Initialise the runner function with passed args, kwargs.
        '''

        # Retrieve args/kwargs here; and fire processing using them
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))
        else:
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            self.signals.finished.emit()  # Done



class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        layout = QVBoxLayout()
        b = QPushButton("START!")
        b.pressed.connect(self.runner)
        layout.addWidget(b)
        w = QWidget()
        w.setLayout(layout)
        self.setCentralWidget(w)
        self.show()

        self.threadpool = QThreadPool()
        print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())


    def execute_this_fn(self, progress_callback):
        command_list=[run1, run2, run3, run4]
        for i in range(4):
            command_list[i]()

        return "Done."
    #this not printing the global list.
    def print_output(self, uart_list):
        print(uart_list)

    def thread_complete(self):
        print("THREAD COMPLETE!")

    def runner(self):
        print("You pressed the Test button")
        # Pass the function to execute
        worker = Worker(self.execute_this_fn) # Any other args, kwargs are passed to the run function
        worker.signals.result.connect(self.print_output)
        worker.signals.finished.connect(self.thread_complete)

        # Execute
        self.threadpool.start(worker)

app = QApplication([])
window = MainWindow()
app.exec_()
7
  • 1
    You are confusing the concepts of concurrency: One thing is multithreading and another thing is parallelism. The idea of concurrency is to do several tasks, and between it there are several strategies: multithreading where several tasks are executed in the same process that is to say they are executed sharing the same memory, and the parallelism where the tasks are executed using resources not shared as cores example. Qt does not support parallelism but multithreading. I recommend you review information regarding the different types of concurrency Commented Mar 17, 2019 at 20:16
  • 1
    What is the output you expect ?, If you realize the 4 tasks are running sequentially in another thread so the print is sequential, I do not see any error Commented Mar 17, 2019 at 20:19
  • Alright, thanks for the information. My understanding was wrong. I know thread runs in the same process and share the memory such as stack and heap. Ok, I know now QT does not support parallelism. Commented Mar 17, 2019 at 20:20
  • I expected the output such as job1, job4, job 3, job2. Where as in pthreads threads do not run in sequence they run in random way that's what I expected. Commented Mar 17, 2019 at 20:22
  • 1
    How many threads have you created? You have only created a single thread and all the tasks are executed in the same thread, if you had created 4 threads you would get what you want Commented Mar 17, 2019 at 20:24

1 Answer 1

4

You have to create a Worker (QRunnable) for each task, in your case you have only created one for it the tasks are executed sequentially:

from PyQt5 import QtCore, QtGui, QtWidgets

import time
import traceback, sys

uart_result = ['1','2', '3', '4', '5', '6']

#Running all these methods in parallel
@QtCore.pyqtSlot()
def run1():
    print("Job 1")
    return uart_result

@QtCore.pyqtSlot()
def run2():
    print("Job 2")
    return uart_result

@QtCore.pyqtSlot()
def run3():
    print("Job 3")
    return uart_result

@QtCore.pyqtSlot()
def run4():
    print("Job 4")
    return uart_result

class WorkerSignals(QtCore.QObject):
    finished = QtCore.pyqtSignal()
    error = QtCore.pyqtSignal(tuple)
    result = QtCore.pyqtSignal(object)
    progress = QtCore.pyqtSignal(int)


class Worker(QtCore.QRunnable):
    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()
    def run(self):
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))
        else:
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            self.signals.finished.emit()  # Done



class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        b = QtWidgets.QPushButton("START!")
        b.pressed.connect(self.runner)

        w = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(w)
        layout.addWidget(b)
        self.setCentralWidget(w)

    def print_output(self, uart_list):
        print(uart_list)

    def thread_complete(self):
        print("THREAD COMPLETE!")

    def runner(self):
        thread_pool = QtCore.QThreadPool.globalInstance()
        print("Multithreading with maximum %d threads" % thread_pool.maxThreadCount())
        print("You pressed the Test button")
        for task in (run1, run2, run3, run4):
            worker = Worker(task)
            worker.signals.result.connect(self.print_output)
            worker.signals.finished.connect(self.thread_complete)
            thread_pool.start(worker)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
Sign up to request clarification or add additional context in comments.

1 Comment

Perfect, once again you cleared my confusion of using QRunnable. Truly you're great.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.