PyQt5 multithreading still freezes. How to improve performance? - python

I'm trying to write a PyQt application for some image processing algorithms that I have written. The problem is, these take some time and make the app freezes (until it finishes but the user might be compelled to close the app in the mean time).
I'm trying to understand how to implement multi-threading but I just can't seem to make it work. This example just loads a large image on a different thread, but it still makes the whole app freeze. I'm sure I'm making a mistake somewhere.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class App(QWidget):
def __init__(self):
super().__init__()
self._unit_ui()
def _unit_ui(self):
label = QLabel(self)
label.resize(700, 700)
button = QPushButton(self)
self.th = Thread(self)
self.th.change_pixmap.connect(label.setPixmap)
button.pressed.connect(self.th.start)
self.show()
class Thread(QThread):
change_pixmap = pyqtSignal(QPixmap)
def __init__(self, parent=None):
QThread.__init__(self, parent=parent)
self.isRunning = True
def run(self):
pixmap = QPixmap('gridscan.jpg')
self.change_pixmap.emit(pixmap)
app = QApplication(sys.argv)
m = App()
sys.exit(app.exec_())

With the help of eyllanesc I managed to solve it.
I just stopped using QPixmap in my second thread and imported the image with another function, passed it (changing the pyqtSignal(QPixmap) to pyqtSignal(np.ndarray)) and now it works flawlessly!
Thanks!

You can try looking into this answer:
https://stackoverflow.com/a/38003561/9066493
Basically, self.th needs to be moved to a different thread like:
# Setup the worker object and the worker_thread.
self.worker = WorkerObject()
self.worker_thread = QtCore.QThread()
self.worker.moveToThread(self.worker_thread)
self.worker_thread.start()
And then transfer data between the main thread and the worker thread using signal and slots, which you need to connect to a specific method slot
Hope the example helps.

Related

How to call a function periodically while my MainWindow is active?

I have tried doing it with multiprocessing module to no avail. I get the following error:
TypeError: cannot pickle 'MainWindow' object
import time, multiprocessing
from PyQt5 import QtWidgets, QtGui
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
self.show()
def initializeUI(self):
# UI stuff
self.setLayout(QtWidgets.QGridLayout())
dummy_btn = QtWidgets.QPushButton("Ok")
self.layout().addWidget(dummy_btn)
updater = multiprocessing.Process(target=self.update_function, args=[])
updater.start()
def update_function(self):
time.sleep(2)
print("This text, again!")
self.update_function()
app = QtWidgets.QApplication([])
mw = MainWindow()
app.exec_()
the proper way to do this is to not use any sort of parallel mechanism, instead use QTimer.singleshot, as QT doesn't work well with multiprocessing or threading, and if you want to repeat it then you can just connect the function to a Qtimer.timeout signal and set the timer on repeat using Qtimer.start() as in this tutorial
import time, multiprocessing
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QTimer
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
self.show()
def initializeUI(self):
# UI stuff
self.setLayout(QtWidgets.QGridLayout())
dummy_btn = QtWidgets.QPushButton("Ok")
self.layout().addWidget(dummy_btn)
self.timer = QTimer(self)
# self.timer.singleShot(2000,self.update_function) # for one time call only
self.timer.timeout.connect(self.update_function)
self.timer.start(2000) # time in milliseconds.
def update_function(self):
# time.sleep(2) this is not needed
print("This text, again!")
self.update() # this had a typo
app = QtWidgets.QApplication([])
mw = MainWindow()
app.exec_()
Edit: to clarify on working with threads and multiprocessing, if you use multiprocessing for example there are many precautions, such as putting an if __name__ == "__main__": guard on your code, and not use anything that belong to QT inside the subprocesses, and just use it for running things that don't need QT, like reading files and doing calculations.
as for threading, using any QWidget object in another thread other than your main application thread is going to crash your application, you can emit signals from child threads for signaling, but you cannot update the GUI on another thread, so only use QT objects that don't touch the GUI inside threads. (like networking, reading files, and sharing the CPU for extra calculations)

How to structure large pyqt5 GUI without subclassing QThread and using QPushButtons to do long-running tasks

I'm looking at creating a program with a PyQt5 GUI. The program will start with a UI with numerous buttons. These buttons will be used to open other programs/completed long running tasks. I know I need to use QThread, but I am unsure how to structure the programs so that it scales properly.
I've been at this for ages and have read numerous posts/tutorials. Most lean down the subclassing route. In the past, I have managed to create a working program subclassing QThread, but I have since read that this metholodogy is not preferred.
I have a feeling I should be creating a generic worker and passing in a function with *args and **kwargs, but that is not in my skillset yet.
I originally created a thread for each button during the GUI init, but that seemed like it was going to get out of hand quickly.
I am currently at the stage of creating a thread under the slot connected to the button.clicked signal. I am not sure if I then have to have a worker for each button or if I can/should make a generic worker and pass in a function. Note: I have tried to do this but have not been able to do it.
#Import standard modules
import sys
#Import third-party modles
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication, QPushButton, QVBoxLayout, QWidget
class Worker(QObject):
#Custom signals?? or built-in QThread signals?
started = pyqtSignal()
finished = pyqtSignal()
def __init__(self):
super().__init__()
self.started.emit()
#pyqtSlot()
def do_something(self):
for _ in range(3):
print('Threading...')
QThread.sleep(1)
self.finished.emit()
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.initUi()
def initUi(self):
#Create GUI
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget )
self.vertical_layout = QVBoxLayout(self.centralWidget)
self.setWindowTitle('QThread Test')
self.setGeometry(300, 300, 300, 50)
self.button1=QPushButton("Task 1", self, clicked=self._task1_clicked)
self.button2=QPushButton("Task 2", self, clicked=self._task2_clicked)
self.vertical_layout.addWidget(self.button1)
self.vertical_layout.addWidget(self.button2)
self.vertical_layout.addStretch()
def _task1_clicked(self):
print('task1 clicked')
#Create the worker
self.my_worker = Worker()
#Create thread; needs to be done before connecting signals/slots
self.task1_thread = QThread()
#Move the worker to the thread
self.my_worker.moveToThread(self.task1_thread)
#Connect worker and thread signals to slots
self.task1_thread.started.connect(self._thread_started)
self.task1_thread.started.connect(self.my_worker.do_something)
self.my_worker.finished.connect(self._thread_finished)
#Start thread
self.task1_thread.start()
def _task2_clicked(self):
print('task2 clicked')
def _thread_started(self):
print('thread started')
def _thread_finished(self):
print('thread finished')
self.my_worker.isRunning = False
self.task1_thread.quit()
self.task1_thread.wait()
print('The thread is running: ' + str(self.task1_thread.isRunning()))
if __name__ == '__main__':
app = QApplication(sys.argv)
form = Window()
form.show()
app.exec_()
The above seems to work, but I feel like I have stumbled on to it and it is not the correct way of doing this. I do not want this to be my 'go-to' method if it is completely wrong. I'd like to be able to generate more complicated (more buttons doing things) programs compared to a one button/one task program.
In addition, I can't seem to get the QThread started and finished signals to fire without basically making them custom built signals. This is one reason I think I am going about this wrong.
from PyQt5 import QtCore
class AsyncTask(QtCore.QThread):
taskDone = QtCore.pyqtSignal(dict)
def __init__(self, *, task, callback=None, parent = None):
super().__init__(parent)
self.task = task
if callback != None:
self.taskDone.connect(callback)
if callback == None:
callback = self.callback
self.start()
def run(self):
try:
result = self.task()
print(result)
self.taskDone.emit(result)
except Exception as ex:
print(ex)
def callback(self):
print('callback')
Please try code above, call like this:
AsyncTask(task=yourTaskFunction, callback=yourCallbackFunction)

PySide: Emiting signal from QThread in new syntax

I'm trying (and researching) with little success to emit a signal from a working Qthread to the main window. I don't seem to understand how I should go about this in the new syntax.
Here's a simple example.
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
class Dialog(QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
button = QPushButton("Test me!")
layout = QVBoxLayout()
layout.addWidget(button)
self.setLayout(layout)
#self.button.clicked.connect(self.test) ----> 'Dialog' object has no attribute 'button'
self.connect(button, SIGNAL('clicked()'), self.test)
self.workerThread = WorkerThread()
def test(self):
self.workerThread.start()
QMessageBox.information(self, 'Done!', 'Done.')
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
time.sleep(5)
print "Thread done!"
app = QApplication(sys.argv)
dialog = Dialog()
dialog.show()
app.exec_()
I understand that if I didn't have another thread I'd create the signal inside the Dialog class and connect it in the __init__ but how can I create a custom signal that can be emitted from WorkerThread and be used test()?
As a side question. You can see it commented out of the code that the new syntax for connecting the signal errors out. Is it something in my configurations?
I'm on OsX El Capitan, Python 2.7
Any help is highly appreciated! Thanks a lot
TL:DR: I'd like to emmit a signal from the WorkerThread after 5 seconds so that the test function displays the QMessageBox only after WorkingThread is done using the new syntax.
Ok, it's been a long day trying to figure this out. My main resource was this: http://www.matteomattei.com/pyside-signals-and-slots-with-qthread-example/
In the new syntax, in order to handle signals from different threads, you have to create a class for your signal like so:
class WorkerThreadSignal(QObject):
workerThreadDone = Signal()
This is how the WorkerThread end up looking like:
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
self.workerThreadSignal = WorkerThreadSignal()
def run(self):
time.sleep(3)
self.workerThreadSignal.workerThreadDone.emit()
And for the connections on the Dialog class:
self.workerThread = WorkerThread()
self.buttonn.clicked.connect(self.test)
and:
self.workerThreadSignal = WorkerThreadSignal()
self.workerThread.workerThreadSignal.workerThreadDone.connect(self.success)
def success(self):
QMessageBox.warning(self, 'Warning!', 'Thread executed to completion!')
So the success method is called once the signal is emitted.
What took me the longest to figure out was this last line of code. I originally thought I could connect directly to the WorkerThreadSignal class but, at least in this case, it only worked once I backtracked it's location. From the Dialog init to WorkerThread init back to the WorkerThreadSignal. I took this hint from the website mentioned above.
I find strange that I have to create the same local variables on both classes, maybe there's a way to create one global variable I can refer to instead all the current solution but it works for now.
I hope this helps someone also stuck in this process!
PS: The syntax problem for the connection was also solved. So everything is written with the new syntax, which is great.

How to display progress without multi-threading

Let's say I have a PyQt program that goes through a given directory, looks for *JPEG images, and does some processing every time it finds one. Depending on the size of the selected directory, this may take from some seconds to minutes.
I would like to keep my user updated with the status - preferably with something like "x files processed out of y files" . If not, a simple running pulse progress bar by setting progressbar.setRange(0,0) works too.
From my understanding, in order to prevent my GUI from freezing, I will need a seperate thread that process the images, and the original thread that updates the GUI every interval.
But I am wondering if there is any possible way for me to do both in the same thread?
Yes, you can easily do this using processEvents, which is provided for this exact purpose.
I have used this technique for implementing a simple find-in-files dialog box. All you need to do is launch the function that processes the files with a single-shot timer, and then periodically call processEvents in the loop. This is should be good enough to update a counter with the number of files processed, and also allow the user to cancel the process, if necessary.
The only real issue is deciding on how frequently to call processEvents. The more often you call it, the more responsive the GUI will be - but this comes at the cost of considerably slowing the processing of the files. So you may have to experiment a little bit in order to find an acceptable compromise.
UPDATE:
Here's a simple demo that shows how the code could be structured:
import sys, time
from PyQt5 import QtWidgets, QtCore
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton('Start')
self.progress = QtWidgets.QLabel('0')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.progress)
self.button.clicked.connect(self.test)
self._stop = False
self._stopped = True
def test(self):
if self._stopped:
self._stop = False
self.progress.setText('0')
self.button.setText('Stop')
QtCore.QTimer.singleShot(1, self.process)
else:
self._stop = True
def process(self):
self._stopped = False
for index in range(1, 1000):
time.sleep(0.01)
self.progress.setText(str(index))
if not index % 20:
QtWidgets.qApp.processEvents(
QtCore.QEventLoop.AllEvents, 50)
if self._stop:
break
self._stopped = True
self.button.setText('Start')
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I could not achieve the thing you need without multi threading and this is not possible because gui can be only updated in main thread. Below is an algorithm how I did this with multithreading.
Let's say you have your application processing images. Then there are the following threads:
Main thread (that blocks by GUI/QApplication-derived classes.exec())
Timer with, for example, 1 second interval which updates a variable and calls a slot in GUI thread which updates a variable in user interface.
A thread which is processing images on your pc.
def process(self):
self._status = "processing image 1"
....
def _update(self):
self.status_label.setText(self._status)
def start_processing(self, image_path):
# create thread for process and run it
# create thread for updating by using QtCore.QTimer()
# connect qtimer triggered signal to and `self._update()` slot
# connect image processing thread (use connect signal to any slot, in this example I'll stop timer after processing thread finishes)
#pyqtSlot()
def _stop_timer():
self._qtimer.stop()
self._qtimer = None
_update_thread.finished.connect(_stop_timer)
In pyqt5 it is possible to assign a pyqtvariable from a one nested thread(first level). So you can make your variable a pyqtvariable with setter and getter and update gui in a setter or think how you can do this by yourself.
You could just use the python threading module and emit a signal in your threaded routine.
Here's a working example
from PyQt4 import QtGui, QtCore
import threading
import time
class MyWidget(QtGui.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.computeButton = QtGui.QPushButton("Compute", self)
self.progressBar = QtGui.QProgressBar()
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.computeButton)
layout.addWidget(self.progressBar)
self.computeButton.clicked.connect(self.compute)
self.valueChanged.connect(self.progressBar.setValue)
def compute(self):
nbFiles = 10
self.progressBar.setRange(0, nbFiles)
def inner():
for i in range(1, nbFiles+1):
time.sleep(0.5) # Process Image
self.valueChanged.emit(i) # Notify progress
self.thread = threading.Thread(target = inner)
self.thread.start()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())

Communicate from Main Application Thread to Seperate QThread

I have this basic app working. It creates a new thread, and starts it. Then it uses signals to communicate back to the main thread for something else to happen.
My question is how do I pass data from the main thread to the new thread that is created, this part really doesn't make sense to me. Or is there another way altogether to do threading back and forth. Essentially the main thread and the new thread will run for the entire life of the application, so they need to communicate back and forth.
As a note I am a web developer so native apps are new to me. Also I am still new to qt and pyqt so not sure how to do this.
import sys
from PyQt4 import QtGui
from PyQt4.QtCore import QThread, pyqtSignal
class Thread(QThread):
message_recieved = pyqtSignal(object)
def run(self):
self.message_recieved.emit('hello')
class Main(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self,parent)
self.initUI()
self.thread = Thread()
self.thread.message_recieved.connect(self.message)
self.thread.start()
def message(self, msg):
print msg
def initUI(self):
self.setGeometry(100, 100, 800, 600)
self.setWindowTitle("Test App")
def main():
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
You shouldn't subclass QThread. You should have a worker sent on the thread you created.
Have a look at this link to get best practices regarding threading in Qt:
http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
The example is in C++, but can be easily translated to Python.
Good luck!

Categories

Resources