PyQt5. Controlling GUI from separate thread [duplicate] - python

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

Related

different ways of calling class method with button clicks

I am exploring how to call class methods in PyQt applications. As a test, I created a widget that initializes a worker, puts it on a separate thread, and starts the thread. I also created two buttons:
The left button connects to the run function of the worker directly
The right button connects to a widget method that calls the worker run function
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
from time import sleep
class Worker(QObject):
worker_finished = pyqtSignal()
def __init__(self):
super().__init__()
#pyqtSlot()
def run(self):
print("Sleeping")
for ii in range(5):
print(ii)
sleep(1)
print("Finished sleeping")
class MainApp(QWidget):
def __init__(self):
super().__init__()
self._threads = []
#Create the worker
self.worker = Worker()
layout = QHBoxLayout(self)
#Move the worker on the thread and start
self.put_on_thread_and_start(self.worker)
#This button calls worker.run() directly
button = QPushButton("Call \"\"run\"\" method directly")
button.clicked.connect(self.worker.run)
layout.addWidget(button)
#This button calls a widget method, which calls worker.run()
button = QPushButton("Call \"\"run\"\" method through this widget class method")
button.clicked.connect(self.make_the_worker_run)
layout.addWidget(button)
def put_on_thread_and_start(self, worker_class):
myThread = QThread()
self._threads.append(myThread)
worker_class.moveToThread(myThread)
print("Starting thread...")
myThread.start()
def make_the_worker_run(self):
self.worker.run()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec_())
When I click the left button, the worker executes in the background, as expected. However, when I click the right button the widget freezes until the worker has finished running. What's the difference between the two approaches?
When a signal is emitted to a function (slot) and that signal is emitted, Qt detects if the signal emission and the receiving objects are in the same thread. If they are not, the slot is executed in the receiver's thread and control is immediately returned in the sender instead. This is what allows to use threads on Qt without blocking the "main Qt thread" (which is responsible of keeping the UI responsive).
A very important thing to understand is that Qt is able to detect what thread emitted the signal and on what thread the slot is, then it decides if the slot can be directly executed or not.
In your first case, the button (which is going to emit the signal) and the self.worker.run are on different threads and Qt knows that when it tries to call run; the result is that the function will be executed in the other thread.
In the second case, Qt only knows about make_the_worker_run, which from its perspective is in the same thread of the button: Qt doesn't know anything about what you actually do in that function. The fact that run is in a method of an object that has been moved to another thread doesn't mean anything, and the result is that the function will be executed in the main thread, hence the blocking.
Read more about this topic on the Qt docs about Threads and QObjects.
There is an incorrect conception of how threads, QThread, signals, etc. work.
When a QObject is moved to a QThread it only tells the Qt eventloop that if it invokes a slot of that QObject it must be executed in the thread that manages the QThread (if the Qt::QueuedConnection or Qt::AutoConnection flag is used in the connection). And how does the Qt eventloop invoke the functions? Well, for this use the signals, timers, QMetaObject::invokedMethod, QEvents, etc.
The above explains why the first method works: By using the clicked signal to invoke "run" it notifies the eventloop that it should invoke the method, and using the previous rule it will invoke the thread that manages the QThread.
In the second method instead you are invoking it directly in the main thread which blocks the eventloop of the main thread freezing the GUI.

PyQt GUI freezes until threads are finished while multithreading in 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.

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.

Multiprocessing in Python tkinter

How to run multiple processes in python without multithreading? For example consider the following problem:-
We have to make a Gui,which has a start button which starts a function(say, prints all integers) and there is a stop button, such that clicking it stops the function.
How to do this in Tkinter?
Then you need to bind the Button widget with the function which starts the working thread. For example:
import time
import threading
import Tkinter as tk
class App():
def __init__(self, root):
self.button = tk.Button(root)
self.button.pack()
self._resetbutton()
def _resetbutton(self):
self.running = False
self.button.config(text="Start", command=self.startthread)
def startthread(self):
self.running = True
newthread = threading.Thread(target=self.printints)
newthread.start()
self.button.config(text="Stop", command=self._resetbutton)
def printints(self):
x = 0
while self.running:
print(x)
x += 1
time.sleep(1) # Simulate harder task
With the self.running approach, you can end the thread gracefully only by changing its value. Note that the use of multiple threads serves to avoid blocking the GUI while printints is being executed.
I have read this previous question and I suppose why you explicitly asked here for a solution without multithreading. In Tkinter this solution can be used in a scenario where the other threads have to communicate with the GUI part. For example: filling a progressbar while some images are being rendered.
However, as it has been pointed in the comments, this approach is too complex for just printing numbers.
Here you can find a lot of information and more examples about Tkinter.
Edit:
Since your new question has been closed, I'll change the code here to clarify the last point.
Did you try to used multiprocessing module? Seems to be the one you're looking for.

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