I'm managing some long running tasks using signals and slots to track progress and such. I had a Run class that starts like this:
from PySide import QtCore, QtGui
class Run(QtCore.QObject):
running = QtCore.Signal(bool)
finished = QtCore.Signal(object)
status = QtCore.Signal(str)
message = QtCore.Signal(object)
progress = QtCore.Signal(float)
time_remaining_update = QtCore.Signal(str)
initialized = QtCore.Signal()
failed = QtCore.Signal(object)
pitch_update = QtCore.Signal(float)
def __init__(self, parent=None):
super().__init__(parent=parent)
#... rest of the class ...
I recently refactored some things and wrote a Task class which includes a Run instance as an attribute. I would like to "echo" many of the Run's signals upstream. This is my current solution:
class Task(QtCore.QObject):
#Signals echoed from Run
running = QtCore.Signal(bool)
status = QtCore.Signal(str)
message = QtCore.Signal(object)
progress = QtCore.Signal(float)
time_remaining_update = QtCore.Signal(str)
initialized = QtCore.Signal()
def __init__(self, run, parent=None):
super().__init__(parent=parent)
self.run = run
#Echo signals from run
signals = ['running', 'status', 'message', 'progress', 'time_remaining_update', 'initialized']
for signal in signals:
#Re-emit the signal
getattr(self.run, signal).connect(lambda *args: getattr(self, signal).emit(*args))
#... rest of class ...
I confirmed that the lambda *args: func(*args) pattern will avoid passing anything to func if nothing is passed to the lambda, which is necessary to obey the different signals' signatures.
Is this the best way to handle "echoing" or "bouncing" signals from one QObject to another? I'm basically trying to preserve some of the Run API while changing/adding other aspects with my Task API.
I realize that I can get all of the Run API by subclassing and overriding things I want to change, but I didn't want to subclass since Task handles restarting the run after failure. I'd like to keep the run logic separate from the code that handles restarting the run. There are also Run signals and methods that I don't want propagated by Task.
The only more elegant solution I can think of is to automate the creation of the Task signals... maybe make a echo_signals decorator that takes a QObject and a list of signal names that then does these things?
Is there a Qt feature for implementing this more elegantly? Any suggestions?
In Qt you can connect signals to other signals, unless I'm missing something regarding your example.
Related
I have a PyQt GUI application with a main window and several workers of different types. I want to display notifications from the workers in the main window, something like:
class MainWindow(QMainWindow):
messageToDisplaySignal = pyqtSignal(str)
...
def displayStatus(self, s):
self.infoLabel.setText(s)
class Worker1(QThread):
...
def run(self):
self.parent.messageToDisplaySignal.emit('Worker1 started')
...
I saw recommendations to send only signals created by the object itself, but this is violated here. If I create messageToDisplaySignal in Worker1, I will also need to implement similar signals in all worker classes and to subscribe to them all in MainWindow. Is there a more proper way to go?
What is the exact reason of these recommendations (to send only signals created by the object itself) is another interesting question. What exactly will be bad otherwise?
The only thing that matters is which specific thread the slot is called in. Qt does not support GUI-related operations of any kind outside the main thread, so it's not merely matter of "recommendations". Your displayStatus slot updates a label, which is not guaranteed to be a thread-safe operation. If you want to confirm that it's being called safely, some basic debugging can easily achieve that:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
messageToDisplaySignal = pyqtSignal(str)
def __init__(self):
super().__init__()
self.infoLabel = QLabel(self)
self.setCentralWidget(self.infoLabel)
self.messageToDisplaySignal.connect(self.displayStatus)
self.worker = Worker1(self)
self.worker.start()
def displayStatus(self, s):
print(f'slot: {int(QThread.currentThreadId())}')
self.infoLabel.setText(s)
class Worker1(QThread):
def run(self):
print(f'worker: {int(QThread.currentThreadId())}')
self.sleep(2)
self.parent().messageToDisplaySignal.emit('Worker1 started')
print(f'main: {int(QThread.currentThreadId())}')
app = QApplication(['Test'])
window = MainWindow()
window.show()
app.exec_()
Output:
main: 140526289762112
worker: 140526172448320
slot: 140526289762112
So the slot is called in the main thread, which is what you want to see. The reason why this works is that Qt automatically detects which thread the signal is emitted from at runtime. If the sender and receiver are in different threads, the signal will be posted as an event to the event-queue of the receiving thread. When control returns to the receiving thread, the signal-event will be processed, and any connected slots will be called at that time.
According this example : link ,I want to implement a thread based small application within a GUI. I have two files TestMain.py and TestManager.py (Worker class), which are connected with each other over sys.path.append('...'). In TestMain.py, I load my GUI, create thread and signal/slot connections, which act on functions in TestManager.py. In TestManager.py I do some calculations and want to visualize them in a QTextEditor at the same time. To do this, I have written following code:
TestMain.py
sys.path.append('../app/source')
from TestManager import *
class TestMain(QMainWindow):
def __init__(self, parent= None):
super(TestMain, self).__init__(parent)
self.manager= TestManager()
self.thread= QThread()
self.manager.moveToThread(self.thread)
self.manager.finished.connect(self.thread.quit)
self.thread.started.connect(self.manager.first_slot)
self.thread.start()
self.manager.printFirstSignal.connect(self.print_onTextEdit)
self.offlinePushButton.clicked.connect(self.offline_slot)
def offline_slot(self):
manager.do_some_calc(self)
def print_onTextEdit(self, str):
self.outputTextEdit.append(str)
Manager.py
sys.path.append('../app/source')
from TestMain import *
class TestManager(QObject): #QtCore.QThread
finished= pyqtSignal()
printFirstSignal= pyqtSignal(str)
def __init__(self, parent= None):
super(TestManager, self).__init__(parent)
def first_slot(self):
self.printFirstSignal.emit('TEST')
self.finished.emit()
def do_some_calc(self):
do_someting()
TestManager.first_slot("do_some_calc")
If I do self.thread.started.connect(self.manager.first_slot) in TestMain and emit the pyqtSignal in function first_slot() in TestManager, then I can update my GUI without any problem. But if I call a slot (e.g do_some_calc()) in TestManager and want to also update the GUI from there, than I get a segmentation fault. Even if I create a separate signal for this function, it does not work. Cause I call this function in TestMain self.offlinePushButton.clicked.connect(self.offline_slot)
My question is. How can I call any function in TestManager internally or from MainTest so that I can still update the GUI?
Thank you for any suggestion
I'm having troubles using PyQt4 slots/signals.
I'm using PyLIRC and I'm listening for button presses on a remote. This part I have gotten to work outside of Qt. My problem comes when emitting the signal from the button listening thread and attempting to call a slot in the main thread.
My button listener is a QObject initialized like so:
buttonPressed = pyqtSignal(int)
def __init__(self):
super(ButtonEvent, self).__init__()
self.buttonPressed.connect(self.onButtonPressed)
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
The onButtonPressed slot is internal to the button listener for testing purposes.
To move the button listener to another thread to do the work, I use the following:
event = ButtonEvent()
eventThread = QThread()
event.moveToThread(eventThread)
eventThread.started.connect(event.run)
Then in the main thread, I have my VideoTableController class that contains the slot in the main thread that doesn't get called. Inside of __init__ I have this:
class VideoTableController(QObject):
def __init__(self, buttonEvent):
buttonEvent.buttonPressed.connect(self.onButtonPressed)
Where onButtonPressed in this case is:
#pyqtSlot(int)
def onButtonPressed(self, bid):
print 'handling button press'
if bid not in listenButtons: return
{ ButtonEnum.KEY_LEFT : self.handleBack,
#...
So when I start the event thread, it starts listening properly. When I press a button on the remote, the onButtonPressed slot internal to the ButtonEvent class is properly called, but the slot within VideoTableController, which resides in the main thread, is not called. I started my listening thread after connecting the slot to the signal, and I tested doing it the other way around, but to no avail.
I have looked around, but I haven't been able to find anything. I changed over to using QObject after reading You're doing it wrong. Any help with this is greatly appreciated. Let me know if you need anything else.
EDIT: Thanks for the responses! Here is a big chunk of code for you guys:
ButtonEvent (This class uses singleton pattern, excuse the poor coding because I'm somewhat new to this territory of Python also):
import pylirc
from PyQt4.QtCore import QObject, pyqtSignal, QThread, pyqtSlot
from PyQt4 import QtCore
class ButtonEvent(QObject):
"""
A class used for firing button events
"""
_instance = None
_blocking = 0
_isListening = False
buttonPressed = pyqtSignal(int)
def __new__(cls, configFileName="~/.lircrc", blocking=0, *args, **kwargs):
if not cls._instance:
cls._instance = super(ButtonEvent, cls).__new__(cls, args, kwargs)
cls._blocking = blocking
if not pylirc.init("irexec", configFileName, blocking):
raise RuntimeError("Problem initilizing PyLIRC")
cls._isListening = True
return cls._instance
def __init__(self):
"""
Creates an instance of the ButtonEvent class
"""
super(ButtonEvent, self).__init__()
self.buttonPressed.connect(self.button)
### init
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
def stopListening(self):
print 'stopping'
self._isListening = False
#pyqtSlot(int)
def button(self, bid):
print 'Got ' + str(bid)
def setupAndConnectButtonEvent(configFileName="~/.lircrc", blocking=0):
"""
Initializes the ButtonEvent and puts it on a QThread.
Returns the QThread it is running on.
Does not start the thread
"""
event = ButtonEvent().__new__(ButtonEvent, configFileName, blocking)
eventThread = QThread()
event.moveToThread(eventThread)
eventThread.started.connect(event.run)
return eventThread
Here is the VideoTableController:
from ControllerBase import ControllerBase
from ButtonEnum import ButtonEnum
from ButtonEvent import ButtonEvent
from PyQt4.QtCore import pyqtSlot
from PyQt4 import QtCore
class VideoTableController(ControllerBase):
listenButtons = [ ButtonEnum.KEY_LEFT,
ButtonEnum.KEY_UP,
ButtonEnum.KEY_OK,
ButtonEnum.KEY_RIGHT,
ButtonEnum.KEY_DOWN,
ButtonEnum.KEY_BACK ]
def __init__(self, model, view, parent=None):
super(VideoTableController, self).__init__(model, view, parent)
self._currentRow = 0
buttonEvent = ButtonEvent()
buttonEvent.buttonPressed.connect(self.onButtonPressed)
self.selectRow(self._currentRow)
#pyqtSlot(int)
def onButtonPressed(self, bid):
print 'handling button press'
if bid not in listenButtons: return
{ ButtonEnum.KEY_LEFT : self.handleBack,
ButtonEnum.KEY_UP : self.handleUp,
ButtonEnum.KEY_OK : self.handleOk,
ButtonEnum.KEY_RIGHT : self.handleRight,
ButtonEnum.KEY_DOWN : self.handleDown,
ButtonEnum.KEY_BACK : self.handleBack,
}.get(bid, None)()
And here is my startup script:
import sys
from PyQt4 import QtCore, QtGui
from ui_main import Ui_MainWindow
from VideoTableModel import VideoTableModel
from VideoTableController import VideoTableController
from ButtonEvent import *
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.buttonEvent = ButtonEvent()
self.bEventThread = setupAndConnectButtonEvent()
model = VideoTableModel("/home/user/Videos")
self.ui.videoView.setModel(model)
controller = VideoTableController(model, self.ui.videoView)
self.bEventThread.start()
def closeEvent(self, event):
self.buttonEvent.stopListening()
self.bEventThread.quit()
event.accept()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
buttonEvent = ButtonEvent()
myapp = Main()
myapp.show()
sys.exit(app.exec_())
It turns out I was just making a foolish Python mistake. The signal was being emitted correctly, and the event loop was running properly in all threads. My problem was that in my Main.__init__ function I made a VideoTableController object, but I did not keep a copy in Main, so my controller did not persist, meaning the slot also left. When changing it to
self.controller = VideoTableController(model, self.ui.videoView)
Everything stayed around and the slots were called properly.
Moral of the story: it's not always a misuse of the library, it may be a misuse of the language.
It seems that the quickest workaround would be change your ButtonEvent code here:
...
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
...
to this:
#pyqtSlot()
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
The short explanation to this issue is that PyQt uses a proxy internally, and this way you can make sure to avoid that. After all, your method is supposed to be a slot based on the connect statement.
Right... Now, I would encourage you to give some consideration for your current software design though. It seems that you are using a class in a dedicated thread for handling Qt button events. It may be good idea, I am not sure, but I have not seen this before at least.
I think you could get rid of that class altogether in the future with a better approach where you connect from the push button signals directly to your handler slot. That would not be the run "slot" in your dedicated thread, however, but the cannonical handler.
It is not a good design practice to introduce more complexity, especially in multi-threaded applications, than needed. Hope this helps.
I haven't actually tested this (because I don't have access to your compiled UI file), but I'm fairly certain I'm right.
Your run method of your ButtonEvent (which is supposed to be running in a thread) is likely running in the mainthread (you can test this by importing the python threading module and adding the line print threading.current_thread().name. To solve this, decorate your run method with #pyqtSlot()
If that doesn't solve it, add the above print statement to various places until you find something running in the main thread that shouldn't be. The lined SO answer below will likely contain the answer to fix it.
For more details, see this answer: https://stackoverflow.com/a/20818401/1994235
I'm trying to create a simple threaded application whereby i have a method which does some long processing and a widget that displays a loading bar and cancel button.
My problem is that no matter how i implement the threading it doesn't actually thread - the UI is locked up once the thread kicks in. I've read every tutorial and post about this and i'm now resorting on asking the community to try and solve my problem as i'm at a loss!
Initially i tried subclassing QThread until the internet said this was wrong. I then attempted the moveToThread approach but it made zero difference.
Initialization code:
loadingThreadObject = LoadThread(arg1)
loadingThread = PythonThread()
loadingThreadObject.moveToThread(loadingThread)
loadingThread.started.connect(loadingThreadObject.load)
loadingThread.start()
PythonThread class (apparently QThreads are bugged in pyQt and don't start unless you do this):
class PythonThread (QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def start(self):
QtCore.QThread.start(self)
def run(self):
QtCore.QThread.run(self)
LoadThread class:
class LoadThread (QtCore.QObject):
results = QtCore.Signal(tuple)
def __init__ (self, arg):
# Init QObject
super(QtCore.QObject, self).__init__()
# Store the argument
self.arg = arg
def load (self):
#
# Some heavy lifting is done
#
loaded = True
errors = []
# Emits the results
self.results.emit((loaded, errors))
Any help is greatly appreciated!
Thanks.
Ben.
The problem was with the SQL library I was using (a custom in-house solution) which turned out not to be thread safe and thus performed blocking queries.
If you are having a similar problem, first try removing the SQL calls and seeing if it still blocks. If that solves the blocking issue, try reintroducing your queries using raw SQL via MySQLdb (or the equivalent for the type of DB you're using). This will diagnose whether or not the problem is with your choice of SQL library.
The function connected to the started signal will run the thread which it was connected, the main GUI thread. However, a QThread's start() function executes its run() method in the thread after the thread is initialized so a subclass of QThread should be created and its run method should run LoadThread.load, the function you want to execute. Don't inherit from PythonThread, there's no need for that. The QThread subclass's start() method should be used to start the thread.
PS: Since in this case the subclass of QThread's run() method only calls LoadThread.load(), the run() method could be simply set to LoadThread.load:
class MyThread(QtCore.QThread):
run = LoadThread.load # x = y in the class block sets the class's x variable to y
An example:
import time
from PyQt4 import QtCore, QtGui
import sys
application = QtGui.QApplication(sys.argv)
class LoadThread (QtCore.QObject):
results = QtCore.pyqtSignal(tuple)
def __init__ (self, arg):
# Init QObject
super(QtCore.QObject, self).__init__()
# Store the argument
self.arg = arg
def load(self):
#
# Some heavy lifting is done
#
time.sleep(5)
loaded = True
errors = []
# Emits the results
self.results.emit((loaded, errors))
l = LoadThread("test")
class MyThread(QtCore.QThread):
run = l.load
thread = MyThread()
button = QtGui.QPushButton("Do 5 virtual push-ups")
button.clicked.connect(thread.start)
button.show()
l.results.connect(lambda:button.setText("Phew! Push ups done"))
application.exec_()
I have a QTextEdit component created in the "main" thread of my program
then I start another thread, which will be updating this QTextEdit every x seconds, but then I get this error:
QObject: Cannot create children for a parent that is in a different thread.
this is the way I'm doing it:
def initGui():
#some gui components
global txt_list
txt_list = QtGui.QTextEdit(w)
txt_list.resize(580,400)
txt_list.move(50, 50)
txt_list.setReadOnly(1)
txt_list.setFont(font_consolas)
#more gui components
def update_list():
t_monitor = threading.Thread(target=monitor_vector)
t_monitor.daemon = True
t_monitor.setName('monitor')
t_monitor.start()
def monitor_vector():
#retrieve info...
lock = threading.Lock
lock = True
txt_list.clear() #clear list, to set the new one
txt_list.setText('updated list')
lock = False
this two last lines of code give me the above mentioned error. Can someone give me a clue on how to handle this?
thanks!
One of the main gotcha's about Qt is that you cannot call any QWidget methods from any thread other than the main GUI thread. All of your communication must be done by emitting signals from the extra threads, which will forward to the main gui.
To start, I see you are using globals, and a lack of the self keyword, so I assume you are not using classes. My example will incorporate a class example as well.
This is example is bare bones, just like yours, outlining the direction you might want to take:
from PyQt4 import QtGui, QtCore
class MyWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
...
self.txt_list = QtGui.QTextEdit(self)
self.txt_list.resize(580,400)
self.txt_list.move(50, 50)
self.txt_list.setReadOnly(1)
self.txt_list.setFont(font_consolas)
...
self.monitor = Monitor()
self.monitor.updateText.connect(self._handleTextUpdate)
self.monitor.update_list()
def _handleTextUpdate(self, txt):
self.txt_list.clear()
self.txt_list.setText(txt)
class Monitor(QtCore.QObject):
updateText = QtCore.pyqtSignal(str)
def update_list(self):
t_monitor = Thread(self.monitor_vector, parent=self)
t_monitor.daemon = True
t_monitor.setName('monitor')
t_monitor.start()
def monitor_vector(self):
...
self.updateText.emit('updated list')
class Thread(QtCore.QThread):
def __init__(self, fn, args, kwargs, parent=None):
super(Thread, self).__init__(parent)
self._fn = fn
self._args = args
self._kwargs = kwargs
def run(self):
self._fn(*self._args, **self._kwargs)
The main thing to notice is that the functions running in the thread are emitting signals. They have no knowledge of the QWidgets in the other classes. Your MyWidget class connects the signal to a slot that can update the QLineEdit. When the thread emits a signal, it will be queued into the main thread and executed by the receiving slot.
I also created a simple QThread subclass that can take a function and args, replicating the way the standard lib Thread class works. You should stick to using QThread because it is a QObject subclass, and supports signals and can run an event loop.