PyQt, QThread: GUI freeze with large amount of SIGNAL from another thread (QListWidget massive update) - python-3.x

I have an app that starts the GUI and performs "the heavy part" of the code in another thread using QThread. In this thread I emit a SIGNAL that is connected to the GUI Class and that performs addItem on QListWidget.
There are a massive "signaling" from this thread to the GUI and it "freeze".
Is there a way to avoid this? Have I to use another mini GUI in different thread only for QListWidget?
Thanks
EDIT:
This is the thread that execute the heavy logic
class YourThreadName(QThread):
def __init__(self, some variables):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
# Here there is a for cycle that emits a SIGNAL
for ... :
...
self.emit(SIGNAL("needed_variable"), needed_variable)
...
In the GUI Class there are some methods, particularly:
class GUI(QtGui.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(GUI, self).__init__(parent)
self.setupUi(self)
def ... (self):
...
def start_main_code(self):
self.new_thread = YourThreadName(some variables)
self.connect(self.new_thread, SIGNAL("finished()"), self.done)
self.connect(self.new_thread, SIGNAL("needed_variable"), self.show_variable)
self.new_thread.start()
def show_variable(self, data):
self.QListWidget_object.addItem(data)
def ... (self):
...

The script below is a Minimal, Complete, and Verifiable Example based on the information currently given in your question and comments. It emits data from a worker thread every 10ms and updates a list-widget in the GUI. On my Linux system (using Python-3.6.3, Qt-4.8.7 and PyQt-4.12.1) it does not block or freeze the GUI. There is obviously some flickering whilst the list-widget is being updated, but I am able to select items, scroll up and down, click the button, etc. And if I increase the sleep to 25ms, I don't even get any flickering.
UPDATE:
The performance can be improved by using setUniformItemSizes and sending the messages in batches. On my system, after a slight initial delay, the list populates with fifty thousand items almost instantly.
import sys
from PyQt4 import QtCore, QtGui
class Worker(QtCore.QThread):
message = QtCore.pyqtSignal(object)
def run(self):
batch = []
for index in range(50000):
if len(batch) < 200:
batch.append(index)
continue
self.message.emit(batch)
batch = []
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.listWidget = QtGui.QListWidget()
self.listWidget.setUniformItemSizes(True)
self.button = QtGui.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.listWidget)
layout.addWidget(self.button)
self.worker = Worker()
self.worker.message.connect(self.handleMessages)
def handleMessages(self, batch):
for message in batch:
self.listWidget.addItem('Item (%s)' % message)
def handleButton(self):
if not self.worker.isRunning():
self.worker.start()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 50, 200, 400)
window.show()
sys.exit(app.exec_())

Related

wxPython threading - GUI freezes while doing an animation in GLCanvas

What is the proper way of refreshing a GLCanvas (with heavy 3D geometries) without freezing the GUI?
I'm trying to use a set of GUI elements (buttons, checkboxes) to control a GLCanvas which is refreshed to display a 3D animation. My problem is that the GUI elements are not responsive when the animation loop is on.
I've tried to launch the animation loop in three different ways:
Option 1: within a thread
Option 2: using wx.lib.delayedresult (most likely similar to thread)
Option 3: calling Refresh after the onDraw event
It seems I can get the Option 1 to work, but I need to introduce a sleep delay with time.sleep(xxx) in between the call to the Refresh of the canvas. Otherwise the GUI remains poorly responsive: resizing the window, will end up "graying" the GUI elements, clicking on the checkboxes will trigger events but not the "checking" of the box, the "mouseover" effect over buttons does not work, etc.
I enclose a small working example. In my actual application, I animate a 3D geometry using moderngl, and I noticed that the "sleep" time required varies depending on how heavy the geometry is (as opposed to this example which is extremely lightweight and works with a delay as small as 0.00001s). I'm wondering what I'm missing. Thanks!
import wx
from wx import glcanvas
import wx.lib.delayedresult as delayedresult
from OpenGL.GL import *
import OpenGL.GL.shaders
import time
import numpy as np
from threading import Thread
# --- Option 1: Thread
class TestThread(Thread):
def __init__(self, parent, canvas):
Thread.__init__(self)
self.parent=parent
self.canvas=canvas
self.start() # start the thread
def run(self):
print('Thread running... ')
while self.canvas.animate:
#time.sleep(0.01) # <<<<<<<<<<<< This line needed
self.canvas.Refresh()
print('Tread done ')
class OpenGLCanvas(glcanvas.GLCanvas):
def __init__(self, parent):
glcanvas.GLCanvas.__init__(self, parent, -1, size=(400,400))
self.context = glcanvas.GLContext(self)
self.SetCurrent(self.context)
self.init = False
self.animate = False
self.refreshAfter = False
self.t=0
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event):
wx.PaintDC(self)
if not self.init:
self.InitGL()
self.init = True
self.OnDraw()
def InitGL(self):
glEnable(GL_DEPTH_TEST)
def OnDraw(self):
""" Called at every frame"""
glClearColor(0.1, 0.0, np.mod(self.t,1), 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if self.animate:
self.t+=0.0005 # increment time
# ---- Option 3
if self.refreshAfter:
self.Refresh() # Trigger next frame
self.SwapBuffers()
# --- Option 2: delayed results
def onAnimDelayedEnd(self, thread):
""" Consumer """
print('Delayed result done')
jobID = thread.getJobID()
result = thread.get()
def onAnimDelayedStart(self):
print('Delayed result running... ')
while self.animate:
self.Refresh()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.canvas = OpenGLCanvas(self)
# GUI
self.rot_btn = wx.Button(self, -1, label="Toggle animation", pos=(430, 10))
self.cbDo = wx.CheckBox (self, -1, label="Do something", pos=(430 ,100))
self.radDo = wx.RadioButton(self, -1, label="Do something", pos=(430,140))
self.radDo2= wx.RadioButton(self, -1, label="Do something", pos=(430,180))
# Binding
self.rot_btn.Bind(wx.EVT_BUTTON, self.toggleAnim)
self.radDo.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.radDo2.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.cbDo.Bind(wx.EVT_CHECKBOX , self.doSomething)
def toggleAnim(self, event):
if not self.canvas.animate:
self.canvas.animate = True
# --- Option 1: thread
TestThread(self, self.canvas)
# --- Option 2: delayed result
#delayedresult.startWorker(self.canvas.onAnimDelayedEnd, self.canvas.onAnimDelayedStart, jobID=1)
# --- Option 3: refreshloop
#self.canvas.refreshAfter=True
#self.canvas.Refresh() # set the canvas into an "infinite" refresh loop
else:
self.canvas.animate = False
def doSomething(self, event):
print('Do something')
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="My wx frame", size=(600,400))
self.Bind(wx.EVT_CLOSE, self.on_close)
self.panel = MyPanel(self)
def on_close(self, event):
self.Destroy()
if __name__ == "__main__":
app = wx.App()
frame = MyFrame().Show()
app.MainLoop()

Pyqt threaded processes application crashing on second launch

I made a small test application to practice threading - on the first launch it works fine, with the loop printing to the console while the gui is still running. However, the kernel crashes and restarts if I close the app and then open it again afterwards.
I've had this issue before with pyqt5 applications, and it's usually solved when I add the QCoreApplication.instance() section at the end of my script. I was wondering whether this time there was an error with the way I implemented my threading.
import sys
import time
from PyQt5 import QtWidgets
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtCore import QCoreApplication
class Worker(QObject):
finished = pyqtSignal()
def __init__(self):
super(Worker, self).__init__()
self.working = True
def work(self):
while self.working:
# do stuff here
print("I'm running")
time.sleep(1)
self.finished.emit() # alert our gui that the loop stopped
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 200, 150)
self.setWindowTitle("Program")
self.startbtn = QtWidgets.QPushButton("Start", self)
self.startbtn.resize(self.startbtn.minimumSizeHint())
self.startbtn.move(50, 50)
self.stopbtn = QtWidgets.QPushButton("Stop", self)
self.stopbtn.move(50, 100)
self.stopbtn.resize(self.stopbtn.minimumSizeHint())
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.startbtn.clicked.connect(self.thread.start) # start the thread when we click the start button
self.thread.started.connect(self.worker.work) # begin our worker object's loop
self.stopbtn.clicked.connect(self.stop_loop) # stop the loop on the stop button click
self.worker.finished.connect(self.loop_finished) # do something in the gui when the worker loop ends
def stop_loop(self):
self.worker.working = False
def loop_finished(self):
print('Looped Finished')
if __name__ == "__main__":
app = QCoreApplication.instance() # Fixes error with kernel crashing on second run of application
if app is None: # PyQt doesn't like multiple QApplications in the same process, therefore
app = QtWidgets.QApplication(sys.argv) # apart from the initial run, the first QApplication instance will run
window = Window()
window.show()
app.exec_()

Threading a method for a PyQT GUI

I have a pyqt GUI and a method[BigramClassification()]which causes the GUI to hang for several seconds. Therefore, i figured out threading need to be used. So after reading several tutorials i came up with the following code.
import sys,os
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import QThread
import time
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.lblHistory.setPixmap(QtGui.QPixmap(os.getcwd() + "/historygraph.png"))
self.workerThread=WorkingThread()
self.ui.pushButton.clicked.connect(self.generateDetails)
self.ui.btnsubmitsettings.clicked.connect(self.addDetails)
def generateDetails(self):
self.workerThread.start()
self.ui.lblHistory.setPixmap(QtGui.QPixmap(os.getcwd() + "/historygraph.png"))
self.addPiechart()
self.addWordCloud()
self.summaryText()
def addPiechart(self):
print ("Added")
def addWordCloud(self):
print ("Added")
def addDetails(self):
def summaryText(self):
print("Added")
class WorkingThread(QThread):
def __init__(self, parent=None):
super(self.__class__, self).__init__(parent)
def run(self):
BigramsClassifier()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MyForm()
myapp.show()
sys.exit(app.exec_())
The problem i have is when i run this and click on pushButton the thread starts but also executes the methods after the start() as seen in def generateDetails(self): I need to prepare this code so that the methods in def generateDetails(self): executes after the thread is done executing with the heavy method BigramClassification()execution.
Summary How can i stop the auto execution of the methods in def generateDetails(self): but only after the method BigramClassification() is completed.
EDIT This error is thrown up when i try to close the GUI.
Connect a slot to the thread's finished signal which can performs other actions once the long-running task is completed:
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
...
self.workerThread = WorkingThread()
self.workerThread.finished.connect(self.doOtherStuff)
...
def generateDetails(self):
if not self.workerThread.isRunning():
self.workerThread.start()
def doOtherStuff(self):
self.ui.lblHistory.setPixmap(QtGui.QPixmap(os.getcwd() + "/historygraph.png"))
self.addPiechart()
self.addWordCloud()
self.summaryText()

PySide: Threading causes GUI to crash

I'm trying to learn the basics of threading with PySide, and so put together the below code. What I'm trying to do is launch a thread that will update a QPlainTextEdit widget using a list of string, with a delay between each string. Instead what I'm getting is a crash to desktop, and I can't understand why:
import sys
import time
from PySide import QtCore, QtGui
class Worker(QtCore.QThread):
to_log = QtCore.Signal(str)
def __init__(self, txt, parent=None):
super(Worker, self).__init__(parent)
self.txt = txt
def run(self):
for i in self.txt:
self.to_log.emit(i)
time.sleep(1)
class TestThreadsApp(QtGui.QWidget):
def __init__(self):
super(TestThreadsApp, self).__init__()
self.initUI()
def initUI(self):
self.log = QtGui.QPlainTextEdit()
self.pb = QtGui.QPushButton('Go')
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.log)
hbox.addWidget(self.pb)
self.setLayout(hbox)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Test')
self.show()
self.pb.clicked.connect(self.get_worker)
def get_worker(self):
self.proceed = False
worker = Worker(['This is a test', 'to understand threading'])
worker.to_log.connect(self.to_log)
worker.start()
def to_log(self, txt):
self.log.appendPlainText(txt)
def main():
app = QtGui.QApplication(sys.argv)
ex = TestThreadsApp()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If I update the get_worker() method to the below, it will run, but the QPlainTextEdit widget is updated with all the strings simultaneously, where as the behavior I'm wanting is for the widget to be updated by the threaded processes as each string is emitted - not altogether after both have been emitted:
def get_worker(self):
self.proceed = False
worker = Worker(['This is a test', 'to understand threading'])
worker.to_log.connect(self.to_log)
worker.start()
while not worker.isFinished():
pass
You need to keep a reference to the thread, otherwise it will be garbage-collected as soon as get_worker returns.
So do something like this, instead:
def get_worker(self):
self.worker = Worker(['This is a test', 'to understand threading'])
self.worker.to_log.connect(self.to_log)
self.worker.start()

call function of main thread from secondary thread

I am making a GUI in PyQt for user to create backup of huge data.
The GUI ( main thread ) is taking inputs from user. rsync command ( for backup ) is also being called in main thread hence the window is freezing.
Aim is to try qthread such that app runs without freezing.
My search material :
1 : https://www.youtube.com/watch?v=o81Q3oyz6rg. This video shows how to not freeze GUI by running other task in secondary thread. I've tried it and it works. But it does not help in running the command in worker thread.
Inspite of calling rsync in secondary thread, the gui still freezes. What am I doing wrong ?.
import sys
from PyQt4 import QtCore, QtGui
from backUpUi import Ui_MainWindow
import threading, Queue
class callerThread(QtCore.QThread):
def __init__(self, func, parent=None, *args, **kwargs):
super(callerThread, self).__init__(parent)
self._func = func
self._args = args
self._kwargs = kwargs
def run(self):
self._func(*self._args, **self._kwargs)
class Monitor(QtCore.QObject):
updateText = QtCore.pyqtSignal(str)
def update_list(self):
t_monitor = callerThread(self.monitor_vector, parent=self)
t_monitor.daemon = True
t_monitor.start()
def monitor_vector(self):
self.updateText.emit('updated list')
class backUpMain(QtGui.QMainWindow):
def __init__(self,parent=None):
super(backUpMain, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.connect(self.ui.okButton, QtCore.SIGNAL("clicked()"), self.startThread)
self.ui.cancelButton.released.connect(sys.exit)
self.monitor = Monitor()
def _handlebackUpdate(self, txt):
QtGui.QMessageBox.information(self, "thread started!", txt)
self.ui.logEdit.clear()
self.ui.logEdit.setText(txt)
def startThread(self):
self.monitor = Monitor()
self.monitor.updateText.connect(self._handlebackUpdate)
self.monitor.update_list()
def threadDone(self,text1):
self.ui.logEdit.append("Worker Thread finished processing %s" % text1)
def exitWindow(self):
self.ui.close()
def main():
app = QtGui.QApplication(sys.argv)
dialog = backUpMain()
dialog.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
While searching for answer, qtcentre helped with it.
You need to have seperate class for signals
class MySignal(QtCore.QObject):
sig = QtCore.pyqtSignal(list)
sigStr = QtCore.pyqtSignal(str)
This signal is used to communicate between threads.
to communicate from main thread to worker thread,
create instance of qthread in init of class where Ui is defined
to pass parameters from main thread either in init or where required.
class MyThread(QtCore.QThread):
def __init__(self, parent = None, *args, **kw): . .
self.setData(*args, **kw)
def setData(self, userShotList, inData, outData, dept):
self.userShotList = userShotList . .
This way data is passed from main to worker.
to communicate from worker thread to main thread
class MyThread(QtCore.QThread):
def __init__(self, parent = None, *args, **kw): . . . .
self.signal = MySignal()
where required execute signal with different types ( list, str ...)
defined in MySignal()
def xyz(self): self.signal.sigStr.emit(message)
Hope this helps.

Resources