PyQt GUI freezes until threads are finished while multithreading in Python - python

The code is pretty heavy so I will just post some snippets.
class Program(QtGui.QWidget):
def __init__(self, parent=None):
super(Program, self).__init__(parent)
...
def main():
app = QtGui.QApplication(sys.argv)
global ex
ex = Program()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
And in between I have a bunch of labels, text fields, buttons and 4 QListWidgets inside sublayouts, these sublayouts are added to the grid.
I'm launching variable number of threads depending on input. Normally 4 threads. They are launched in own class:
class myThread(Thread):
def __init__(self, arguments, more_arguments):
threading.Thread.__init__(self)
def work_it(self, argument, more_arguments):
Program.some_function_in_Program_class(ex, arguments)
And from there I call functions inside the Program() class to do the actual changes to the GUI.
Program.generate_results(ex, arguments, arguments2, more_arguments)
In the end it comes down to a list I iterate over and whether I print each element or I use:
my_listbox.addItem(item)
It freezes the GUI until all 4 threads are finished going through the list. Then all the results appear together instead of appearing one by one.
I have done this in Tkinter and I could see one by one list item appear in the ListBox widgets dynamically, without freezing the GUI.
As for managing threads what I'm doing is I'm iterating over a list and according to its length I make a number of threads:
threadlist = []
for i in self.results:
sub_thread = myThread(i, self.results[i])
self.threadlist.append(sub_thread)
swapAndWaitThread = spawnAndWaitThreadsThread(self.threadlist)
swapAndWaitThread.start()
I do this to be able to manage these 4 threads and be able to tell when they are finished.
class spawnAndWaitThreadsThread(Thread):
def __init__(self, threadlist):
threading.Thread.__init__(self)
self.threadlist = threadlist
def run(self):
for thread in self.threadlist:
thread.start()
for thread in self.threadlist:
thread.join()
print "threads finished.. do something"
What am I doing wrong?

I resolved this issue by adding a very simple function in the main Program() class which has only one job - to start a very simple thread which in turn goes back to Program() class and calls a function there which then starts constructing my threads list, sending them to another thread class, etc.
As a result GUI is not freezing anymore.

Related

PyQt5. Controlling GUI from separate thread [duplicate]

This question already has an answer here:
In PyQt, what is the best way to share data between the main window and a thread
(1 answer)
Closed 5 months ago.
I want to produce simple GUI, a label and a push button. When I press the push button it should invoke a separate thread (the GUI unfreezes ). In the thread I have simple loop
for i in range(4):
self.label.setText(str(i))
time.sleep(1)
the loop must change the label text each second. So I expect to see the GUI how the label changes 0, 1, 2, 3 each second. This is my code:
self.pushButton.clicked.connect(self.clicked_1)
self.label.setText("Lubo1")
def clicked_1(self):
for i in range(4):
self.label.setText(str(i))
app.processEvents()
print("Sleep!")
time.sleep(1)
print("Weak up!")
x = threading.Thread(target=clicked_1, args=(1,))
x.start()
The code works exactly as I expect when I comment x = threading.Thread(target=clicked_1, args=(1,)) and uncomment app.processEvents().
However when app.processEvents() is commented and x = threading.Thread(target=clicked_1, args=(1,)) is uncommented the code gives an error:
AttributeError: 'int' object has no attribute 'label'
I don't want to use app.processEvents() since I know this is not the right way. The correct way is to use threading, but I don't know how to overcome the AttributeError: 'int' object has no attribute 'label' error. Please help.
Best Regards, thank you in advance.
Lubomir Valkov
You should never do such a process, Qt does not support gui operations of any kind outside the main thread. Sooner or later, if you call your GUI out of your main thread your app will crash because it calls methods which are not thread-safe. You should always use signals and slots to communicate between threads and main gui.
# your thread class
class ThreadClass(QtCore.QThread):
any_signal = QtCore.pyqtSignal(int)
def __init__(self, time_to_wait, parent=None):
super(ThreadClass, self).__init__(parent)
self.time_wait = time_to_wait
def run(self):
for val in range(self.time_wait):
time.sleep(1)
self.any_signal.emit(val+1)
your GUI
self.pushButton.clicked.connect(self.clicked_1)
self.label.setText("Lubo1")
def clicked_1(self):
self.thread = ThreadClass(time_to_wait=4, parent=None)
self.thread.any_signal.connect(self.update_label)
self.thread.start()
def update_label(self, value):
self.label.setText(str(i))
As you can see you your button start a thread that emit a signal. Your gui captures this signal and do what ever it need to do in the main GUI, but your thread is never changing anything of the main thread

Signals and Slots PyQt clarification

I have noticed that there are a lot of users, myself included, who don't quite grasp the concept of signals and slots in Qt. I was hoping to get some clarification on the following:
#I have a function that runs as soon as the GUI is built, this takes the information from
#a list and puts it into a string which is then uploaded to a texbox. At the bottom of this
#loop, I want it to call a function in the parent thread via signals and slots, as
#recommended by other users.
class MainWindow(QtGui.QMainWindow):
#all the code needed to build the GUI
thread_mythread = threading.Thread(target = self.updateText, args = ())
thread_mythread.start()
def clearText(self):
self.TextEdit.clear()
def updateText(self):
self.trigger.connect(self.clearText)
while True:
self.trigger.emit()
NewString = list.pop(0)
#I think I may have to use append, as setText() is not safe outside of the parent thread
self.TextEdit.append(NewString)
Although probably terribly incorrect, I attempt to use signals. Is this the proper way to do it? I also get an error that says that the Main Window object has no attribute "trigger",why is this?
thank you.
The reason you get that error is exactly the reason described by the error message - the signal trigger has not been defined anywhere in your class. You need to define it before you can emit it.
Signals and slots are used to communicate between different objects. In your example you are trying to do everything from within your MainWindow class and there is no interaction with other objects. You also only need to make the call to connect() once. You would typically call this either in the class constructor or from your main function after instantiating the objects you want to connect together.
Take a look at http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html for some examples of how to use signals and slots properly in PyQt.
For threading, use QThread rather than threading.Thread as it is better integrated with the Qt framework. This post shows some simple examples of how to use QThread in PyQt. The second method (using moveToThread()) is considered to be the most correct way to create new threads.
The basic idea for your kind of problem is:
handle GUI operations from the main thread
handle blocking operations (in your case the while loop) in a separate thread
emit signals from the worker thread to call functions (slots) in the main thread and vice versa
Also note that:
You cannot call any methods of QWidget its descendents from a secondary thread
Signals can also send data if you need to pass it between threads
To add to #user3419537 good answer. A very quick threading example:
from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QThread, \
Q_ARG, Qt, QMetaObject
class MyWorker(QObject):
# define signal
clear = pyqtSignal()
update_text_signal = pyqtSignal(str) # passes a string back
finished = pyqtSignal()
def __init__(self, parent=None):
super(MyWorker, self).__init__(parent)
# Add functions etc.
#pyqtSlot(list)
def update_text(self, string_list):
#Intensive operation
self.clear.emit() # moved outside of while
while(True):
#This is infinite loop so thread runs forever
new_string = self.string_list.pop(0)
self.update_text_signal.emit(new_string) # Fixed this line
#Finished
self.finished.emit()
Then in your MainWindow class
self.my_thread = QThread()
self.handler = MyWorker()
self.handler.moveToThread(self.my_thread)
self.handler.clear.connect(self.clearText)
self.handler.update_text_signal.connect(self.update_line_edit)
self.handler.finished.connect(self.my_thread.quit)
# Start Thread
self.my_thread.start()
#pyqtSlot(str)
def update_line_edit(self, text):
self.TextEdit.append(text)
QMetaObject.invokeMethod(self.handler, 'update_text',
Qt.QueuedConnection,
Q_ARG(list, string_list))
You will need to call self.my_thread.quit() before your application closes to stop the thread and avoid the error: QThread: Destroyed while thread is still running
Please read docs for QMetaObject.invokeMethod.

How to move an object back and forth between QThreads in Pyqt

In my program (using Python 2.7), I create an object containing some important data and methods. Some of the methods are CPU hungry, so in certain cases I move the object to a new QThread for the duration of the CPU intensive methods, then have them come back to the main thread. At a later point, when a CPU intensive method is called, I would like to move the object to another QThread again, however this fails saying "Current thread is not the object's thread".
Here is a trivial example which reproduces the problem:
import sys
from PyQt4 import QtCore, QtGui
from time import sleep
class ClassA(QtGui.QDialog):
def __init__(self):
super(ClassA, self).__init__()
mainLayout=QtGui.QVBoxLayout()
self.lineEdit=QtGui.QLineEdit()
mainLayout.addWidget(self.lineEdit)
self.setLayout(mainLayout)
self.show()
self.obj=ClassC(self)
self.executeProgram()
def executeProgram(self):
self.lineEdit.setText("Starting new thread...")
self.thread=QtCore.QThread()
self.obj.moveToThread(self.thread)
self.thread.started.connect(self.obj.doWork)
self.obj.doingWork.connect(self.updateGui)
self.obj.finished.connect(self.killThread)
self.thread.start()
def updateGui(self,message):
self.lineEdit.setText(message)
def killThread(self):
self.thread.quit()
self.thread.wait()
self.obj.finished.disconnect()
self.executeProgram()
class ClassC(QtCore.QObject):
finished=QtCore.pyqtSignal()
doingWork=QtCore.pyqtSignal(str)
def __init__(self,parent=None):
super(ClassC, self).__init__()
def doWork(self):
for i in range(5):
self.doingWork.emit("doing work: iteration "+str(i))
sleep(1)
self.finished.emit()
if __name__=="__main__":
app=QtGui.QApplication(sys.argv)
obj=ClassA()
app.exec_()
Is it possible to move an object to a different QThread multiple times? If so, how would I fix my code to do this?
Note moveToThread must be called on the thread that the object currently belongs to, so you may need to move the object back to the main thread before moving it to yet another thread.
Add the line mainThread = QtCore.QThread.currentThread() somewhere at the top, and put self.moveToThread(mainThread) right before emitting the finished signal.

Wrap a Python class in an asynchronous PyQt class

I am developing my pet project. It is a small app which downloads my inbox folder from Facebook. I want it to be available in both CLI and GUI (in PyQt) mode. My idea was that I write a class for the communication, and then the front-ends. I know that the downloading process is blocking which is not problem in CLI mode but it is in GUI.
I know that there is QNetworkAccessManager but I would have to re-implement the class that I have already written and then maintain two classes the same time.
I was searching for a while and I came up with one solutions where I subclass QObject and my FB class, implement the signals, than create a QThread object and use moveToThread() method which would be OK, but I must take care of creating and stopping the tread.
Is it possible to wrap my Python class to something to make it behave like QNetworkAccessManager? So the methods would return immediately and the object will emit signals when the data is ready.
Update
Thank you the comments. I have 2 main classes. The first is called SimpleGraph which just hides urllib2.urlopen(). It prepares the query and returns the decoded json got from Facebook. The actual work is happening in FBMDown class:
class FBMDown(object):
def __init__(self, token):
self.graph = SimpleGraph(token)
self.last_msg_count = 0
def _message_count(self, thread_id):
#TODO: Sanitize thread_id
p = {'q': 'SELECT message_count FROM thread WHERE thread_id = {0} LIMIT 1'.format(thread_id)}
self.last_msg_count = int(self.graph.call(params=p, path='fql')['data'][0]['message_count'])
return self.last_msg_count
So here when _message_count is called it returns the number of messages of the given thread id. This method works great in CLI mode, blocking is not a problem there.
I want to wrap this class into a class (if it is possible) which works asynchronous like QNetworkAccessManager, so it would not block the GUI but it would emit a signal when the data is ready.
The only technique I know now is to subclass QObject. It looks like this:
class QFBMDown(QtCore.QObject):
msg_count_signal = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def get_msg_count(self):
#Here happens the IO
time.sleep(2)
self.msg_count_signal.emit(1)
And this is my Windows class:
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.centralwidget = QtGui.QWidget(self)
self.button_getmsgcount = QtGui.QPushButton(self.centralwidget)
self.setCentralWidget(self.centralwidget)
self.i = 0
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.block_indicator)
self.timer.start(20)
#self.worker = QtCore.QThread()
self.fbobj = QFBMDown()
#self.fbobj.moveToThread(self.worker)
#self.worker.start()
self.button_getmsgcount.clicked.connect(self.fbobj.get_msg_count)
self.fbobj.msg_count_signal.connect(self.show_msg_count)
def block_indicator(self):
self.button_getmsgcount.setText(str(self.i))
self.i += 1
def show_msg_count(self, data):
print 'Got {0} msgs in the thread'.format(data)
Now when I press the button the GUI blocks. If I de-comment line 13, 15, 16 (where I create a thread, move fbobj into this thread then start it) it does not block, it works nearly as I want but I have to do everything by hand every time and I have to take care of shutting down the thread (now I implement QMainWindow's closeEvent method where I shut down the thread before quit but I'm sure that this is not the right way to do).
Is it even possible what I want to do? I would not want to implement a new class and then maintain two classes which are doing the same thing.
The reason I am so insisted to do it like this is to make my app work without Qt but give a nice gui for those who don't want to type command line arguments.

Working the function in the background - how? Python and PyQT

I have a relatively large application written in Python and using PyQT as a GUI frontend. The entire application is in one class, in one file.
Here's an example code:
class Application(QMainWindow):
def __init__(self):
super(etc...)
self.connect(self.mainBtn, SIGNAL("clicked()"), self.do_stuff)
def do_stuff(self):
<checking some parameters>
else:
do_some_other_long_stuff()
def do_some_other_long_stuff(self):
500 lines of code of stuff doing
However, this is the problem: when I click the mainBtn, everything goes fine, except the GUI kind of freezes - I can't do anything else until the function is performed (and it's a web scraper so it takes quite a bit of time). When the function do_some_other_long_stuff ends, everything goes back to normal. This is really irritating.
Is there a way to somehow "background" the do_some_other_stuff process? I looked into QThreads and it seems it does just that, however that would require me to rewrite basically all of code, put half of my program in a different class, and therefore have to change all the variable names (when getting a variable from GUI class and putting it in working class)
Duplicate of Handling gui with different threads,
How to keep track of thread progress in Python without freezing the PyQt GUI?, etc.
Your do_stuff() function needs to start up the computing thread and then return. Multi-threading is the name given to running multiple activities in a single process - by definition if something is going on "in the background", it's running on a separate thread. But you don't need to split functions into a different classes to use threads, just be sure that the computing functions don't do anything with the GUI and the main thread doesn't call any of the functions used by the computing thread.
EDIT 10/23: Here's a silly example of running threads in a single class - nothing in the language or the threading library requires a different class for each thread. The examples probably use a separate class for processing to illustrate good modular programming.
from tkinter import *
import threading
class MyApp:
def __init__(self, root):
self.root = root
self.timer_evt = threading.Event()
cf = Frame(root, borderwidth=1, relief="raised")
cf.pack()
Button(cf, text="Run", command=self.Run).pack(fill=X)
Button(cf, text="Pause", command=self.Pause).pack(fill=X)
Button(cf, text="Kill", command=self.Kill).pack(fill=X)
def process_stuff(self): # processing threads
while self.go:
print("Spam... ")
self.timer_evt.wait()
self.timer_evt.clear()
def Run(self): # start another thread
self.go = 1
threading.Thread(target=self.process_stuff, name="_proc").start()
self.root.after(0, self.tick)
def Pause(self):
self.go = 0
def Kill(self): # wake threads up so they can die
self.go = 0
self.timer_evt.set()
def tick(self):
if self.go:
self.timer_evt.set() # unblock processing threads
self.root.after(1000, self.tick)
def main():
root = Tk()
root.title("ProcessingThread")
app = MyApp(root)
root.mainloop()
main()

Categories

Resources