QTimer in worker thread blocking GUI - python

I am trying to create a worker thread whose job is to monitor the status bit of a positioning platform.
To do this I connect a QTimer timeout signal to a function that queries the platform.
class expSignals(QtCore.QObject):
pause=QtCore.pyqtSignal()
class motorpositioner(QtCore.QObject):
def __init__(self):
QtCore.QThread.__init__(self)
self.timer = QtCore.QTimer()
self.timer.start(100)
self.timer.timeout.connect(self.do_it)
self.lock=QtCore.QMutex()
self.running=True
self.stat=0
def do_it(self):
with QtCore.QMutexLocker(self.lock):
#self.stat = self.motors.get_status()
print(self.stat)
time.sleep(5)
#QtCore.pyqtSlot()
def stop1(self):
self.timer.stop()
print('stop heard')
The GUI stuff looks like this:
class MyApp(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.thread=QtCore.QThread(self)
#worker
self.mot=motorpositioner()
# =============================================================================
# Putting buttons and GUI stuff in place
# =============================================================================
self.button=QtWidgets.QPushButton('Derp',self)
layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.button)
self.setLayout(layout)
self.setGeometry( 300, 300, 350, 300 )
# =============================================================================
# Connecting signals
# =============================================================================
self.sig=expSignals()
self.sig2=expSignals()
self.button.clicked.connect(self.stop)
self.sig.pause.connect(self.mot.stop1)
self.sig2.pause.connect(self.thread.quit)
self.mot.moveToThread(self.thread)
self.thread.start()
def stop(self):
self.sig.pause.emit()
def closeEvent(self,event):
self.sig2.pause.emit()
event.accept()
However the way it is written now the GUI is unresponsive. However if I comment out self.timer.timeout.connect(self.do_it) and put do_it in a while(True) loop, the GUI isn't being blocked.
Why is the main thread being blocked when using QTimer?

I do not know what is expSignals() and I think it is not relevant, and neither is the button.
Your code has the following errors:
You are starting the timer before the thread starts, so the task will run on the GUI thread.
QTimer is not a child of motorpositioner so if motorpositioner moves to the new thread QTimer will not. For him to move he must be a son so you must pass him as a parent to self.
I do not know if it is a real error, but you are firing the QTimer every 100 ms but the task takes 5 seconds, although the QMutex helps to have no problems because it is blocked.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import time
class motorpositioner(QtCore.QObject):
def __init__(self):
QtCore.QThread.__init__(self)
self.timer = QtCore.QTimer(self)
self.lock = QtCore.QMutex()
self.running = True
self.stat = 0
def start_process(self):
self.timer.timeout.connect(self.do_it)
self.timer.start(100)
def do_it(self):
with QtCore.QMutexLocker(self.lock):
#self.stat = self.motors.get_status()
print(self.stat)
time.sleep(5)
#QtCore.pyqtSlot()
def stop1(self):
self.timer.stop()
print('stop heard')
class MyApp(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.thread = QtCore.QThread(self)
self.mot = motorpositioner()
self.mot.moveToThread(self.thread)
self.thread.started.connect(self.mot.start_process)
self.thread.start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = MyApp()
ex.show()
sys.exit(app.exec_())

Related

Cannot get qthread example to work

I am totally stuck with this python qt threading tutorial. I have two issues.
1) The longRunning function never executes.
2) When exiting the app, I get QThread: Destroyed while thread is still running and a Python.exe has stopped working.
Any help? Thanks!!
#!/usr/bin/env python2
import sys, time
from PySide.QtGui import *
from PySide import QtCore
class SeperateThread(QtCore.QObject):
finished = QtCore.Signal()
def __init__(self, parent = None):
super(SeperateThread, self).__init__(parent)
self._isRunning = True
def longRunning(self):
end = time.time()+3
while self._isRunning == True:
sys.stdout.write('*')
sys.stdout.flush()
time.sleep(1)
now = time.time()
if now>=end:
self._isRunning=False
self.finished.emit()
def stop(self):
self._isRunning = False
class MainWindow(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self,parent)
centralwidget = QWidget(self)
startButton = QPushButton('Start long (3 seconds) operation',self)
label2 = QLabel('Long batch')
vbox = QVBoxLayout()
vbox.addWidget(startButton)
vbox.addWidget(label2)
self.setCentralWidget(centralwidget)
centralwidget.setLayout(vbox)
obj = SeperateThread()
objThread = QtCore.QThread()
obj.moveToThread(objThread)
objThread.started.connect(obj.longRunning)
def LongOperationStart():
label2.setText('app is running')
startButton.setEnabled(False)
objThread.start()
def LongOperationFinish():
startButton.setEnabled(True)
#GUI start button
startButton.clicked.connect(LongOperationStart)
#Once thread is finished.
objThread.finished.connect(LongOperationFinish)
obj.finished.connect(objThread.quit)
if __name__=='__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Oh ok, I found out what I was doing wrong:
SeperateThread is being garbage collected after the MainWindow constructor returns. Make obj a member of MainWindow so that it persists beyond the constructor.
Thanks!!

Pyqt: Change icon from button

I'm just started with pyqt and i want to change the icon from a button.
But i'm dowing it from another class and pyqt don't like that.
The error is: QPixmap: It is not safe to use pixmaps outside the GUI thread
I know i must using singal en emits.
But i don't know how i must use it for change the icon from a button.
This my code now:
import sys
import time
from PyQt4 import QtGui, QtCore
from pymodbus.exceptions import ConnectionException
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from data.database import Tags, Query_Tags
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.db_engine = create_engine('mysql://modbususer:modbususer#localhost/modbus')
self.db_session = sessionmaker(bind=self.db_engine)
self.db_session = self.db_session()
self.status = 0
self.initUI()
def initUI(self):
self.button = QtGui.QPushButton('Aan', self)
self.button.clicked.connect(self.run)
self.button.move(50,50)
Uit = QtGui.QPushButton('Uit', self)
Uit.clicked.connect(self.off)
Uit.move(150,50)
self.setGeometry(300, 300, 500, 150)
self.setWindowTitle('Quit button')
self.show()
self.worker = WorkThread(self)
self.worker.start()
def run(self):
add_tag = Query_Tags('18', '1')
self.db_session.add(add_tag)
self.db_session.commit()
def off(self):
add_tag = Query_Tags('18', '0')
self.db_session.add(add_tag)
self.db_session.commit()
self.status = 0
print self.store
def change(self):
print "test"
#self.button.setIcon(QtGui.QIcon("/home/stijnb/test/icon.png"))
#self.button.setIconSize(QtCore.QSize(16,16))
def database(self, store):
self.store = store
"""
Thread
"""
class WorkThread(QtCore.QThread):
def __init__(self, layout):
QtCore.QThread.__init__(self)
self.layout = layout
def __del__(self):
self.wait()
def run(self):
self.database = {}
while True:
self.db_engine = create_engine('mysql://modbususer:modbususer#localhost/modbus')
self.db_session = sessionmaker(bind=self.db_engine)
self.db_session = self.db_session()
for result in self.db_session.query(Tags):
self.database[int(result.id)] = {'naam': result.name, 'value': result.value}
self.change_icons()
time.sleep(1)
return
self.terminate()
def change_icons(self):
print self.database[21]['value']
if self.database[21]['value'] == '1':
self.layout.button.setIcon(QtGui.QIcon("/home/stijnb/test/aan.png"))
self.layout.button.setIconSize(QtCore.QSize(16,16))
else:
self.layout.button.setIcon(QtGui.QIcon("/home/stijnb/test/uit.png"))
self.layout.button.setIconSize(QtCore.QSize(16,16))
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You cannot touch GUI elements from any other thread other than the main GUI Thread. That's why Qt introduced messaging (Signals and Slots). You can connect to a signal from your worker thread that will be caught in the main thread, and then in the main thread you can change any element you want.
Here is a very simple example where I demonstrate the concept.
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
# Create a button and set it as a central widget
self.button = QtGui.QPushButton('My Button', self)
self.setCentralWidget(self.button)
# Start the thread
self.worker = WorkThread(self)
self.worker.start()
# Connect the thread's signal "change_text" to
# "self.change_thread" function
self.worker.change_text.connect(self.change_text)
def change_text(self):
# Slots are always executed in the GUI thread so it's safe to change
# anything you want in a slot function. Here we just flip the button
# text from foo to bar and back. But you can change the image or do
# whatever you want.
# Slots can also receive parameters so you can emit from your thread
# which text should be set
self.button.setText('bar' if self.button.text() == 'foo' else 'foo')
class WorkThread(QtCore.QThread):
# Define the signal
change_text = QtCore.pyqtSignal()
def __init__(self, layout):
QtCore.QThread.__init__(self)
def run(self):
while True:
# emit the signal every 3 seconds
self.sleep(3)
self.change_text.emit()
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())

Terminating QThread gracefully on QDialog reject()

I have a QDialog which creates a QThread to do some work while keeping the UI responsive, based on the structure given here: How To Really, Truly Use QThreads; The Full Explanation. However, if reject() is called (due to the user pressing cancel or closing the dialog) while the thread is still running I get an error:
QThread: Destroyed while thread is still running
What I'd like to happen is for the loop in the worker to break early, then do some cleanup in the background (e.g. clear some queues, emit a signal). I've managed to do this with my own "cancel" function, but how do I get it to play nicely with reject() (and all the many ways it could be called)? I don't want the dialog to block waiting for the cleanup - it should just keep running in the background, then exit gracefully.
See sample code below which exhibits the problem. Any help would be greatly appreciated.
#!/usr/bin/env python
from PyQt4 import QtCore, QtGui
import sys
import time
class Worker(QtCore.QObject):
def __init__(self):
QtCore.QObject.__init__(self)
def process(self):
# dummy worker process
for n in range(0, 10):
print 'process {}'.format(n)
time.sleep(0.5)
self.finished.emit()
finished = QtCore.pyqtSignal()
class Dialog(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self)
self.init_ui()
def init_ui(self):
self.layout = QtGui.QVBoxLayout(self)
self.btn_run = QtGui.QPushButton('Run', self)
self.layout.addWidget(self.btn_run)
self.btn_cancel = QtGui.QPushButton('Cancel', self)
self.layout.addWidget(self.btn_cancel)
QtCore.QObject.connect(self.btn_run, QtCore.SIGNAL('clicked()'), self.run)
QtCore.QObject.connect(self.btn_cancel, QtCore.SIGNAL('clicked()'), self.reject)
self.show()
self.raise_()
def run(self):
# start the worker thread
self.thread = QtCore.QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
QtCore.QObject.connect(self.thread, QtCore.SIGNAL('started()'), self.worker.process)
QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.thread.quit)
QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.worker.deleteLater)
QtCore.QObject.connect(self.thread, QtCore.SIGNAL('finished()'), self.thread.deleteLater)
self.thread.start()
def main():
app = QtGui.QApplication(sys.argv)
dlg = Dialog()
ret = dlg.exec_()
if __name__ == '__main__':
main()
Your problem is: self.thread is freed by Python after the dialog is closed or the cancel button is pressed, while Qt thread is still running.
To avoid such situation, you can designate a parent to that thread. For example,
def run(self):
# start the worker thread
self.thread = QtCore.QThread(self)
self.worker = Worker()
self.worker.moveToThread(self.thread)
QtCore.QObject.connect(self.thread, QtCore.SIGNAL('started()'), self.worker.process)
QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.thread.quit)
QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.worker.deleteLater)
QtCore.QObject.connect(self.thread, QtCore.SIGNAL('finished()'), self.thread.deleteLater)
self.thread.start()
Then it will be owned by Qt instead of PyQt and hence won't be collected by GC before it is terminated by Qt gracefully.
Actually, this method just lets Qt not complain and doesn't solve the problem completely.
To terminate a thread gracefully, the common approach is using a flag to inform the worker function to stop.
For example:
class Worker(QtCore.QObject):
def __init__(self):
QtCore.QObject.__init__(self)
def process(self):
# dummy worker process
self.flag = False
for n in range(0, 10):
if self.flag:
print 'stop'
break
print 'process {}'.format(n)
time.sleep(0.5)
self.finished.emit()
finished = QtCore.pyqtSignal()
class Dialog(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.init_ui()
def init_ui(self):
self.layout = QtGui.QVBoxLayout(self)
self.btn_run = QtGui.QPushButton('Run', self)
self.layout.addWidget(self.btn_run)
self.btn_cancel = QtGui.QPushButton('Cancel', self)
self.layout.addWidget(self.btn_cancel)
QtCore.QObject.connect(self.btn_run, QtCore.SIGNAL('clicked()'), self.run)
QtCore.QObject.connect(self.btn_cancel, QtCore.SIGNAL('clicked()'), self.reject)
QtCore.QObject.connect(self, QtCore.SIGNAL('rejected()'), self.stop_worker)
self.show()
self.raise_()
def stop_worker(self):
print 'stop'
self.worker.flag = True
def run(self):
# start the worker thread
self.thread = QtCore.QThread(self)
self.worker = Worker()
self.worker.moveToThread(self.thread)
QtCore.QObject.connect(self.thread, QtCore.SIGNAL('started()'), self.worker.process)
QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.thread.quit)
QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.worker.deleteLater)
QtCore.QObject.connect(self.thread, QtCore.SIGNAL('finished()'), self.thread.deleteLater)
self.thread.start()

Python: How can I refresh QLCDNumbers + emiting again after stopping

I want to ask how is it possible to refresh QLCDNumbers after I have started some measures.
I created an GUI thread to connect the signals to QLCDNumbers like that:
class BtDialog(QtGui.QDialog, Dlg):
def __init__(self):
QtGui.QDialog.__init__(self)
self.setupUi(self)
self.thread = WorkerThread()
#Configure slots
self.connect(self.startButton, QtCore.SIGNAL("clicked()"), self.onStart)
self.connect(self.stopButton, QtCore.SIGNAL("clicked()"), self.onStop)
#QLCDNumber Slot
self.connect(self.thread, self.thread.voltage, self.lcdVoltage.display)
def onStart(self):
self.thread.start()
def onStop(self):
self.emit(self.thread.voltage, 0) #Trying to refresh
abort()
Here I connect two buttons, one for starting the worker thread and the other to stop the process. When I stop the process I want to refresh the QLCDNumber by displaying '0' but it doesn't work.
In worker thread i initialize the signal like that:
def __init__(self, parent = None):
QtCore.QThread.__init__(self, parent)
self.voltage = QtCore.SIGNAL("voltage")
And when the process runs I emit the signal with
self.emit(self.voltage, volt_act)
after measuring. That works so far. But after stopping when I want to start the worker process again the signal doesn't emit to QLCDNumber again. For that I have to restart the GUI. How can I fix the two problems of mine for that I want to refresh QLCDNumber and over that after stopping and refreshing emitting the signal again?
Can't tell where the issue is from the code you posted, but this should help you modify it, also checkout the docs for new-style signal/slot connections and further reference (modal dialogs, timers, etc):
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import time
from PyQt4 import QtGui, QtCore
class MyThread(QtCore.QThread):
countChange = QtCore.pyqtSignal(int)
countReset = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyThread, self).__init__(parent)
self.stopped = QtCore.QEvent(QtCore.QEvent.User)
def start(self):
self.stopped.setAccepted(False)
self.count = 0
super(MyThread, self).start()
def run(self):
while not self.stopped.isAccepted():
self.count += 1
self.countChange.emit(self.count)
time.sleep(1)
self.countReset.emit(0)
def stop(self):
self.stopped.setAccepted(True)
class MyWindow(QtGui.QDialog):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.lcdNumber = QtGui.QLCDNumber(self)
self.pushButtonStart = QtGui.QPushButton(self)
self.pushButtonStart.setText("Start")
self.pushButtonStart.clicked.connect(self.on_pushButtonStart_clicked)
self.pushButtonStop = QtGui.QPushButton(self)
self.pushButtonStop.setText("Stop")
self.pushButtonStop.clicked.connect(self.on_pushButtonStop_clicked)
self.pushButtonDone = QtGui.QPushButton(self)
self.pushButtonDone.setText("Done")
self.pushButtonDone.clicked.connect(self.on_pushButtonDone_clicked)
self.layoutHorizontal = QtGui.QHBoxLayout(self)
self.layoutHorizontal.addWidget(self.lcdNumber)
self.layoutHorizontal.addWidget(self.pushButtonStart)
self.layoutHorizontal.addWidget(self.pushButtonStop)
self.layoutHorizontal.addWidget(self.pushButtonDone)
self.thread = MyThread(self)
self.thread.countChange.connect(self.lcdNumber.display)
self.thread.countReset.connect(self.lcdNumber.display)
#QtCore.pyqtSlot()
def on_pushButtonStart_clicked(self):
self.thread.start()
#QtCore.pyqtSlot()
def on_pushButtonStop_clicked(self):
self.thread.stop()
#QtCore.pyqtSlot()
def on_pushButtonDone_clicked(self):
sys.exit()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.exec_()
sys.exit(app.exec_())

Advice on GUI timer to display background thread's elapsed time?

Issue
I have a PyQt GUI where the user presses a button to start a background thread (workerThread, which is subclassed from QThread). I would like to have a timer display (in the form of a QLabel) to show how much time has elapsed since workerThread started, and I would like this timer to stop as soon as the workerThread exits.
Possible solution
I've thought about creating another independent thread (timerThread) that uses a QTimer to send a signal to a slot to update the QLabel in the main GUI thread with the elapsed time every 1 second. This timerThread will exit as soon as it receives a terminate signal from workerThread.
However, I'd have to start timerThread at the same time as WorkerThread, and I'm not sure how to this.
Question
Is there an easier way to do this? Is QTimer even the right approach to start with?
Here is one way of doing this. In this example, myThread starts the timerThread as a child process when it's run method is called. It's timeElapsed signal is connected to the timerThreads timeElapsed signal. The timerThread will check each second while the parent isRunning and emit a timeElapsed signal if True:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import time
from PyQt4 import QtCore, QtGui
class timerThread(QtCore.QThread):
timeElapsed = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(timerThread, self).__init__(parent)
self.timeStart = None
def start(self, timeStart):
self.timeStart = timeStart
return super(timerThread, self).start()
def run(self):
while self.parent().isRunning():
self.timeElapsed.emit(time.time() - self.timeStart)
time.sleep(1)
class myThread(QtCore.QThread):
timeElapsed = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(myThread, self).__init__(parent)
self.timerThread = timerThread(self)
self.timerThread.timeElapsed.connect(self.timeElapsed.emit)
def run(self):
self.timerThread.start(time.time())
iterations = 3
while iterations:
print "Running {0}".format(self.__class__.__name__)
iterations -= 1
time.sleep(2)
class myWindow(QtGui.QWidget):
def __init__(self):
super(myWindow, self).__init__()
self.button = QtGui.QPushButton(self)
self.button.setText("Start Threading!")
self.button.clicked.connect(self.on_button_clicked)
self.label = QtGui.QLabel(self)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.button)
self.layout.addWidget(self.label)
self.myThread = myThread(self)
self.myThread.timeElapsed.connect(self.on_myThread_timeElapsed)
self.myThread.finished.connect(self.on_myThread_finished)
#QtCore.pyqtSlot()
def on_button_clicked(self):
self.myThread.start()
#QtCore.pyqtSlot(int)
def on_myThread_timeElapsed(self, seconds):
self.label.setText("Time Elapsed: {0}".format(seconds))
#QtCore.pyqtSlot()
def on_myThread_finished(self):
print "Done"
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('myWindow')
main = myWindow()
main.show()
sys.exit(app.exec_())

Categories