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:
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_())
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.
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.
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.
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.
- 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:
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_())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With