Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QThread in Qt on Python

I employed QThread using a worker object after reading extended discussions on how QThread should be used and overwriting its run method via subclassing it is not the proper method. However, in the method I intend to use I need to pass an additional function argument which is not available at the time the thread is initiated and the worker is pushed to the thread using moveToThread. This information (argument) is available at the time a button is pressed and conveys the information on which object to move.

In the full version of my code there are three separate controllers for three separate objects and you can find a minimal working example below demonstrating what I tried to pass the argument. The code is also available on pastebin and the line numbers of interest are 10-28, 46-50 and 133-135.

So far I have tried using a lambda constructor in the line connecting to the actual function in the worker. That is the line self.thread.started.connect(self.obj.moveLeftIncrement) then tried to use slots, but I did not understand them quite well. Furthermore, despite using QThread sometimes the GUI hangs and there are errors following program exit one of which is as follows:

Process finished with exit code -1073740791 (0xC0000409)

My questions are below:

  1. How can I pass the parameter at runtime and/or use slots?
  2. How can I prevent program errors upon exit?
  3. Why does subclassing QThread directly works in this case although it is not recommended?

from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *
import sys
import time

class Worker(QObject):
    finished = Signal(int)

    @Slot(str)
    def moveLeftIncrement(self, controller_name):
        # controller_name = "Controller 1"
        print("Controller name is ", controller_name)
        if controller_name == "Controller 1":
            print("Starting controller 1")
            time.sleep(2)
            print("Finishing sleep")
        elif controller_name == "Controller 2":
            print("Starting controller 2")
            time.sleep(2)
            print("Finishing sleep")
        elif controller_name == "Controller 3":
            print("Starting controller 3")
            time.sleep(2)
            print("Finishing sleep")
        else:
            raise Exception("No such controller found!")
        self.finished.emit(0)


class Window(QWidget):
    """ Inherits from QWidget """
    def closeEvent(self, *args, **kwargs):
        print("\nClosing")

    def __init__(self):
        super().__init__()
        self.CONTINUOUS_MOVE_SWITCH = False
        self.title = 'Control Controllers'
        self.left = 10
        self.top = 10
        self.width = 320
        self.height = 100
        self.AxesMapping = [0, 1, 2, 3]
        self.initUI()
        self.thread = QThread()
        self.obj = Worker()
        self.obj.moveToThread(self.thread)
        self.thread.started.connect(self.obj.moveLeftIncrement)
        self.obj.finished.connect(self.thread.quit)

    def initUI(self):
        """ Initializes the GUI either using the grid layout or the absolute position layout"""
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        Comp1 = self.createGridLayout("Controller 2")
        windowLayout = QGridLayout()

        windowLayout.addWidget(Comp1, 0, 0)
        self.setLayout(windowLayout)
        self.show()

    def createGridLayout(self, controller):
        """Creates a grid layout for the buttons"""
        box_size = QSize(640, 440)
        HGroupBox = QGroupBox(controller)
        layout = QGridLayout()
        layout.addWidget(self.createButton("left", controller), 2, 1)
        layout.addWidget(self.createButton("right", controller), 2, 3)
        layout.addWidget(self.createButton("forward", controller), 1, 2)
        layout.addWidget(self.createButton("backward", controller), 3, 2)
        HGroupBox.setLayout(layout)
        HGroupBox.setFixedSize(box_size)
        return HGroupBox

    def createButton(self, name, controller):
        """Creates a button with the specified size"""
        button_size = QSize(100, 40)
        icon_size = 40
        button = QPushButton()
        button.Name = name
        button.Controller = controller
        button.Moving = 0
        button.clicked.connect(lambda: self.buttonPresssed(button))
        button.setFixedSize(button_size)
        return button

    def moveLeftIncrement(self, controller, button):
        if controller == "Controller 1":
            time.sleep(2)
        elif controller == "Controller 2":
            time.sleep(2)
        elif controller == "Controller 3":
            time.sleep(2)
        else:
            raise Exception("No such controller found!")

    def moveRightIncrement(self, controller, button):
        if controller == "Controller 1":
            time.sleep(2)
        elif controller == "Controller 2":
            time.sleep(2)
        elif controller == "Controller 3":
            time.sleep(2)
        else:
            raise Exception("No such controller found!")

    def moveForwardIncrement(self, controller, button):
        if controller == "Controller 1":
            time.sleep(2)
        elif controller == "Controller 2":
            time.sleep(2)
        elif controller == "Controller 3":
            time.sleep(2)
        else:
            raise Exception("No such controller found!")

    def moveBackwardIncrement(self, controller, button):
        if controller == "Controller 1":
            time.sleep(2)
        elif controller == "Controller 2":
            time.sleep(2)
        elif controller == "Controller 3":
            time.sleep(2)
        else:
            raise Exception("No such controller found!")

    def buttonPresssed(self, button):
        name = button.Name
        if hasattr(button, 'Controller'):
            controller = button.Controller
            print("The controller selected is", controller)
        if name == 'left':
            self.thread.start()
        elif name == 'right':
            print("Moved controller right for a single step")
            self.moveRightIncrement(controller, button)
        elif name == 'forward':
            self.moveForwardIncrement(controller, button)
            print("Moved controller forward for a single step")
        elif name == 'backward':
            self.moveBackwardIncrement(controller, button)
            print("Moved controller backward for a single step")
        elif name == "up":
            print("Moving controller up for a single step")
            self.moveUpIncrement(controller, button)
        elif name == "down":
            print("Moving controller down for a single step")
            self.moveDownIncrement(controller, button)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Window()
    sys.exit(app.exec_())
like image 637
Vesnog Avatar asked Jan 27 '19 23:01

Vesnog


People also ask

How do you use QThread?

To use it, prepare a QObject subclass with all your desired functionality in it. Then create a new QThread instance, push the QObject onto it using moveToThread(QThread*) of the QObject instance and call start() on the QThread instance. That's all.

Is PyQt multithreaded?

Multithreading in PyQt With QThread. Qt, and therefore PyQt, provides its own infrastructure to create multithreaded applications using QThread . PyQt applications can have two different kinds of threads: Main thread.

How do you use multiple threads in Python?

To use multithreading, we need to import the threading module in Python Program. A start() method is used to initiate the activity of a thread. And it calls only once for each thread so that the execution of the thread can begin.

How do you wait for QThread to finish?

Normally, with Qt you will have a QApplication based class with an event loop with signals and slots, that will not exit from the main function until you want to. In that case you can simply connect the QThread::finish() signal to a slot that checks if all threads are done.


1 Answers

- How can I pass the parameter at runtime and/or use slots?

If you want to invoke a slot of an object that is in another thread you must use signals since it is thread-safe.

- How can I prevent program errors upon exit?

In your case the error is caused because you have a thread running and you have not stopped it, a possible option is to use the closeEvent to stop it.

- Why does subclassing QThread directly works in this case although it is not recommended?

Not that it is not recommended, but it is very limited, there are better options like using a worker that lives in another thread so we can have different methods and not just do a task in the run method. Also with the option of the worker you can have several objects living in a thread.


In the case of the worker, the methodology is as follows:

  • Create a QThread and start the thread that handles.
  • Create an object that will live in the other thread and move the other thread
  • Connect the signals that invoke the slots of the object, and also connect the signals that will send the information of the object to the GUI.
  • Emit the signals they will invoke when necessary.

Considering the above, the solution is:

from PySide2 import QtCore, QtGui, QtWidgets
import time

class Worker(QtCore.QObject):
    error = QtCore.Signal()

    @QtCore.Slot(str)
    def moveLeftIncrement(self, controller):
        if controller == "Controller 1":
            time.sleep(2)
        elif controller == "Controller 2":
            time.sleep(2)
        elif controller == "Controller 3":
            time.sleep(2)
        else:
            self.error.emit("No such controller found!")

    @QtCore.Slot(str)
    def moveRightIncrement(self, controller):
        if controller == "Controller 1":
            time.sleep(2)
        elif controller == "Controller 2":
            time.sleep(2)
        elif controller == "Controller 3":
            time.sleep(2)
        else:
            self.error.emit("No such controller found!")

    @QtCore.Slot(str)
    def moveForwardIncrement(self, controller, button):
        if controller == "Controller 1":
            time.sleep(2)
        elif controller == "Controller 2":
            time.sleep(2)
        elif controller == "Controller 3":
            time.sleep(2)
        else:
            self.error.emit("No such controller found!")

    @QtCore.Slot(str)
    def moveBackwardIncrement(self, controller, button):
        if controller == "Controller 1":
            time.sleep(2)
        elif controller == "Controller 2":
            time.sleep(2)
        elif controller == "Controller 3":
            time.sleep(2)
        else:
            self.error.emit("No such controller found!")

class Window(QtWidgets.QWidget):
    leftClicked = QtCore.Signal(str)
    rightClicked = QtCore.Signal(str)
    forwardClicked = QtCore.Signal(str)
    backwardClicked = QtCore.Signal(str)

    def __init__(self):
        super().__init__()
        self.CONTINUOUS_MOVE_SWITCH = False
        self.title = 'Control Controllers'
        self.left, self.top, self.width, self.height = 10, 10, 320, 100
        self.AxesMapping = [0, 1, 2, 3]

        self.initUI()

        self.thread = QtCore.QThread(self)
        self.thread.start()
        self.obj = Worker()
        self.obj.moveToThread(self.thread)
        self.leftClicked.connect(self.obj.moveLeftIncrement)
        self.rightClicked.connect(self.obj.moveRightIncrement)
        self.forwardClicked.connect(self.obj.moveForwardIncrement)
        self.backwardClicked.connect(self.obj.moveBackwardIncrement)
        self.obj.error.connect(self.on_error)

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        Comp1 = self.createGridLayout("Controller 2")
        windowLayout = QtWidgets.QGridLayout(self)
        windowLayout.addWidget(Comp1, 0, 0)

    def createGridLayout(self, controller):
        """Creates a grid layout for the buttons"""
        box_size = QtCore.QSize(640, 440)
        HGroupBox = QtWidgets.QGroupBox(controller)
        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.createButton("left", controller), 2, 1)
        layout.addWidget(self.createButton("right", controller), 2, 3)
        layout.addWidget(self.createButton("forward", controller), 1, 2)
        layout.addWidget(self.createButton("backward", controller), 3, 2)
        HGroupBox.setLayout(layout)
        HGroupBox.setFixedSize(box_size)
        return HGroupBox

    def createButton(self, name, controller):
        button_size = QtCore.QSize(100, 40)
        icon_size = 40
        button = QtWidgets.QPushButton()
        button.Name = name
        button.Controller = controller
        button.Moving = 0
        button.clicked.connect(self.buttonPresssed)
        button.setFixedSize(button_size)
        return button

    @QtCore.Slot()
    def buttonPresssed(self):
        button = self.sender()
        name = button.Name
        if hasattr(button, 'Controller'):
            controller = button.Controller
            print("The controller selected is", controller)
            if name == 'left':
                self.leftClicked.emit(controller)
            elif name == 'right':
                print("Moved controller right for a single step")
                self.rightClicked.emit(controller)
            elif name == 'forward':
                print("Moved controller forward for a single step")
                self.forwardClicked.emit(controller)
            elif name == 'backward':
                print("Moved controller backward for a single step")
                self.backwardClicked.emit(controller)

    @QtCore.Slot(str)
    def on_error(self, error):
        print(error)

    def closeEvent(self, event):
        self.thread.quit()
        self.thread.wait()
        super(Window, self).closeEvent(event)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    ex = Window()
    ex.show()
    sys.exit(app.exec_())
like image 142
eyllanesc Avatar answered Sep 20 '22 03:09

eyllanesc