PySide6 QThread still freezing main GUI - multithreading

I am currently trying to implement some threading functionality in my PySide6 GUI application. I followed a tutorial to try to get started (link is here), and I cannot seem to get it to work. Although that tutorial uses PyQt not PySide, the classes and structure is still similar, and it does seem to launch on another thread. Still though, it freezes the main GUI, which is not desired when this actually faces users.
Here is a sample of my code:
class Worker(QObject):
finished = Signal(str)
progress = Signal(int)
def run(self, file):
"""Long-running task." that calls a separate class for computation""
b = SeparateClass()
b.doComputation()
self.finished.emit()
class DataPlotting(QMainWindow):
def __init__(self):
self.thread = QThread()
self.worker = Worker()
self.report_builder = QPushButton('Call class that threads')
self.report_builder.setEnabled(False)
self.report_builder.clicked.connect(self.qthread_test)
def qthread_test(self):
file = 'some_file.txt'
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run(file))
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
return
This does accomplish the work that is in the Worker class and spit out the desired results, but it freezes the GUI. I am not really sure what I am doing wrong, as this approach is what has been suggested to prevent freezing GUIs for heavy computation.
Is there something that I am straight up missing? Or am I going about this the wrong way? Any help or guidance is appreciated

I am assuming that you make the appropriate calls to the super class during __init__ for your subclasses of QMainWindow and the QObject.
When your code executes self.thread.started.connect(self.worker.run(file)) that line it runs the function self.worker.run(file) immediately and assigns the result of that function, which is None, as the connected slot to the thread.started signal. Instead of passing the file path as a parameter you can assign it to the worker instance and have the run method grab the path from self during execution.
For example you can try something like this:
class Worker(QObject):
finished = Signal(str)
progress = Signal(int)
def run(self):
"""Long-running task." that calls a separate class for computation"""
file = self.some_file
b = SeparateClass()
b.doComputation()
self.finished.emit()
class DataPlotting(QMainWindow):
def __init__(self):
self.report_builder = QPushButton('Call class that threads')
self.report_builder.setEnabled(False)
self.report_builder.clicked.connect(self.qthread_test)
self.threads = []
def qthread_test(self):
worker = Worker()
thread = QThread()
worker.some_file = 'some_file.txt'
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
thread.start()
self.threads.append(thread)
return

Related

QGraphicsEffect on pyqt, blinking a button

I am building a GUI on python and pyqt.
The GUI has a lot of pushbuttons, generated through class LED, meaning each led has 3 buttons, for an n number of leds.
In a few of the buttons, I want an effect that changes the opacity of the pushbutton, in a loop from 0 to 1 and back again, so it disappears and appears. I need only one process to manage all, so the effect starts at same time for every button and all blink at the same time.
I've managed to achieve that, through qgraphicseffect in a thread, iterating through a list.
The problem is that after a few minutes, the effect stops, although the thread is still running (print(opacity_level)). more pushbuttons with the effect makes even shorter duration. Clicking any button, even others without effect, restarts the gui animation.
My small research in threading on pyqt made me implement this thread manager, although I do not fully understand it.
class WorkerSignals(QtCore.QObject):
finished = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(tuple)
result = QtCore.pyqtSignal(object)
progress = QtCore.pyqtSignal(tuple)
class Worker(QtCore.QRunnable):
'''
Worker thread
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
'''
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
# Add the callback to our kwargs
self.kwargs['progress_callback'] = self.signals.progress
#pyqtSlot()
def run(self):
'''
Initialise the runner function with passed args, kwargs.
'''
# Retrieve args/kwargs here; and fire processing using them
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
Next the leds class
class LEDs:
def __init__(self,name,group,frame):
self.opacity_effect = QtWidgets.QGraphicsOpacityEffect()
self.button_auto = QtWidgets.QPushButton()
self.button_auto.setObjectName("button_auto_neutral")
self.button_auto.clicked.connect(lambda state, x=self: self.AutoMode())
def AutoMode(self):
print(self.name,"Automode")
if len(settings.blink) ==0: # start thread only if no previous thread, both thread and
this reference the size of settings.blink, so not ideal.
print("start thread")
settings.ledAutomode()
settings.blink.append(self)
And finally the settings class, which has the thread with the effect performing action. There is a second thread, which handles the icon of the button, accordingly with a timetable.
class Settings:
def __init__(self):
self.blink=[]
def ledAutomode(self):
def blink(progress_callback):
print("opacity")
op_up=[x/100 for x in range(0,101,5)]
op_down=op_up[::-1]; op_down=op_down[1:-1]; opacity=op_up+op_down
while len(self.blink) !=0:
for i in opacity:
print(i)
QtCore.QThread.msleep(80)
for led in self.blink:
led.opacity_effect.setOpacity(i)
def timeCheck(progress_callback):
while len(self.blink) != 0:
QtCore.QThread.msleep(500)
for led in self.blink:
matrix = [v for v in settings.leds_config[led.group][led.name]["Timetable"]]
matrix_time=[]
...
# some code
...
if sum(led_on_time):
led.button_auto.setObjectName("button_auto_on")
led.button_auto.setStyleSheet(ex.stylesheet)
else:
led.button_auto.setObjectName("button_auto_off")
led.button_auto.setStyleSheet(ex.stylesheet)
QtCore.QThread.msleep(int(30000/len(self.blink)))
worker = Worker(blink) # Any other args, kwargs are passed to the run function
ex.threadpool.start(worker)
worker2 = Worker(timeCheck) # Any other args, kwargs are passed to the run function
ex.threadpool.start(worker2)
So, perhaps a limitation on qgraphicseffect, or some problem with the thread (although its keeps printing), or I made some error.
I've read about subclassing the qgraphicseffect but I don't know if that solves the problem.
If anyone has another implementation, always eager to learn.
Grateful for your time.
Widgets are not thread-safe.
They cannot be created nor accessed from external threads. While it "sometimes" works, doing it is wrong and usually leads to unexpected behavior, drawing artifacts and even fatal crash.
That said, you're making the whole process incredibly and unnecessarily convoluted, much more than it should be, most importantly because Qt already provides both timed events (QTimer) and animations.
class FadeButton(QtWidgets.QPushButton):
def __init__(self):
super().__init__()
self.effect = QtWidgets.QGraphicsOpacityEffect(opacity=1.0)
self.setGraphicsEffect(self.effect)
self.animation = QtCore.QPropertyAnimation(self.effect, b'opacity')
self.animation.setStartValue(1.0)
self.animation.setEndValue(0.0)
self.animation.setDuration(1500)
self.animation.finished.connect(self.checkAnimation)
self.clicked.connect(self.startAnimation)
def startAnimation(self):
self.animation.stop()
self.animation.setDirection(self.animation.Forward)
self.animation.start()
def checkAnimation(self):
if not self.animation.value():
self.animation.setDirection(self.animation.Backward)
self.animation.start()
else:
self.animation.setDirection(self.animation.Forward)
If you want to synchronize opacity amongst many widgets, there are various possibilities, but a QVariantAnimation that updates all opacities is probably the easier choice:
class LEDs(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = QtWidgets.QHBoxLayout(self)
self.animation = QtCore.QVariantAnimation()
self.animation.setStartValue(1.0)
self.animation.setEndValue(0.0)
self.animation.setDuration(1500)
self.animation.valueChanged.connect(self.updateOpacity)
self.animation.finished.connect(self.checkAnimation)
self.buttons = []
for i in range(3):
button = QtWidgets.QPushButton()
self.buttons.append(button)
layout.addWidget(button)
effect = QtWidgets.QGraphicsOpacityEffect(opacity=1.0)
button.setGraphicsEffect(effect)
button.clicked.connect(self.startAnimation)
# ... as above ...
def updateOpacity(self, opacity):
for button in self.buttons:
button.graphicsEffect().setOpacity(opacity)
Note that you shouldn't change the object name of a widget during runtime, and doing it only because you want to update the stylesheet is wrong. You either use a different stylesheet, or you use the property selector:
QPushButton {
/* default state */
background: #ababab;
}
QPushButton[auto_on="true"] {
/* "on" state */
background: #dadada;
}
class FadeButton(QtWidgets.QPushButton):
def __init__(self):
super().__init__()
# ...
self.setProperty('auto_on', False)
def setAuto(self, state):
self.setProperty('auto_on', state)
self.setStyleSheet(self.styleSheet())

How to implement events in Python?

Can't figure out how to implement event system.
I am doing project with tkinter.
And I need to use events.
How to have events like Java or C# events?
I searched a lot but can't figure out the right way.
Here is the event class I am trying to implement.
class Event(object):
def __init__(self):
self.handlers = []
def add(self, handler):
self.handlers.append(handler)
return self
def remove(self, handler):
self.handlers.remove(handler)
return self
def fire(self, sender, earg=None):
for handler in self.handlers:
value = handler()
self.remove(handler)
return value
__iadd__ = add
__isub__ = remove
__call__ = fire
Here is Car class
class Car:
_speed = 0
events = Event()
def speed_up(self):
self._speed += 10
def speed_down(self):
self._speed -= 10
def get_speed(self):
return self._speed
And for last there is Window class (tkinter window)
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.car = Car()
tk.Button(self, text="Speed Up", command=self.increase_speed).grid(sticky="nsew")
tk.Button(self, text="Speed Down", command=self.decrease_speed).grid(sticky="nsew")
self.speed_label = tk.Label(self, text="0")
self.speed_label.grid(sticky="nsew")
self.mainloop()
def increase_speed(self):
self.car
def decrease_speed(self):
pass
Here is tkinter window:
I want to accomplish:
1) On Speed Up button click "speed_up" should be added to events.
2) It should change value of self.speed_label.
3) It should be something like c# / Java events or c# delagetes.
Trying to learn this new concept to me. But having difficult time implementing this...
UPDATED!
I was searching / editing and came up with a solution.
Don't know if this solution is good.
I will ask my teacher if this is a good way for implementing events.
But for now the code look like this:
import tkinter as tk
class Observer():
_observers = []
def __init__(self):
self._observers.append(self)
self._observed_events = []
def observe(self, event_name, callback_fn):
self._observed_events.append({'event_name' : event_name, 'callback_fn' : callback_fn})
class Event():
def send(self, event_name, *callback_args):
for observer in Observer._observers:
for observable in observer._observed_events:
if observable['event_name'] == event_name:
observable['callback_fn'](*callback_args)
def receive(self, event_name, *callback_args):
for observer in Observer._observers:
for observable in observer._observed_events:
if observable['event_name'] == event_name:
response = observable['callback_fn'](*callback_args)
return response
class Car(Observer):
def __init__(self):
Observer.__init__(self)
self._current_speed = 0
def speed(self):
self._current_speed += 10
def slow(self):
self._current_speed -= 10
def current(self):
return self._current_speed
class Window(tk.Tk):
def __init__(self):
super().__init__()
self._car = Car()
self.store()
self.events = Event()
tk.Button(self, text="Speed Up", command=lambda:self.change_speed("speed")).grid(sticky="nsew")
tk.Button(self, text="Slow Down", command=lambda:self.change_speed("slow")).grid(sticky="nsew")
self.label = tk.Label(self, text=0)
self.label.grid()
self.settings()
def store(self):
self._car.observe("speed", self._car.speed)
self._car.observe("slow", self._car.slow)
self._car.observe("current", self._car.current)
def settings(self):
self.mainloop()
def change_speed(self, event):
self.events.send(event)
self.label.config(text=self.events.receive("current"))
Window()
Without fully analyzing the code in the question, I'd say you're in the right direction by using function callbacks. That's because Python, as far as I know, does not have a native implementation of events.
Some useful libraries or examples that build on this can be seen in articles such as the observer pattern, mimicking events or in answers to a related question.
This is the simplest code I can think of that illustrates the callback concept, without arguments:
def on_notify():
print("OK, I'm up-to-date")
def do_something(update):
# do whatever I need to do
print("I've changed\n")
update()
do_something(on_notify)
Which outputs:
I've changed
OK, I'm up-to-date
Our "worker" function takes a function parameter to be called when an event happens. In this case it's only one, but a list could be used, so that we have many watchers, which is what the other more complete examples do.
Also relevant are event objects, a mechanism for objects to communicate between threads, which is something worth considering for user interfaces. My guess is that most libraries and frameworks out there which implement the "missing events" functionality are based on either or both of these core methods.
You can find a good explanation on how to implement observers and events in a language that doesn't have them by default in here, it is written in c++, but the implementation is almost the same in python.
I would probably have used a list of observers for each event, so instead of the event having to search for its observers in the observers' list it just has to cycle through its personal list of observers to notify them. If the event has specific parameters that are important like the id of the entity who activated it you could include it on the notification, each observer decides what to do with that, they can simply do nothing if that information isn't relevant to them.

Python - How can I implement a 'stoppable' thread?

There is a solution posted here to create a stoppable thread. However, I am having some problems understanding how to implement this solution.
Using the code...
import threading
class StoppableThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""
def __init__(self):
super(StoppableThread, self).__init__()
self._stop_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
How can I create a thread that runs a function that prints "Hello" to the terminal every 1 second. After 5 seconds I use the .stop() to stop the looping function/thread.
Again I am having troubles understanding how to implement this stopping solution, here is what I have so far.
import threading
import time
class StoppableThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""
def __init__(self):
super(StoppableThread, self).__init__()
self._stop_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
def funct():
while not testthread.stopped():
time.sleep(1)
print("Hello")
testthread = StoppableThread()
testthread.start()
time.sleep(5)
testthread.stop()
Code above creates the thread testthread which can be stopped by the testthread.stop() command. From what I understand this is just creating an empty thread... Is there a way I can create a thread that runs funct() and the thread will end when I use .stop(). Basically I do not know how to implement the StoppableThread class to run the funct() function as a thread.
Example of a regular threaded function...
import threading
import time
def example():
x = 0
while x < 5:
time.sleep(1)
print("Hello")
x = x + 1
t = threading.Thread(target=example)
t.start()
t.join()
#example of a regular threaded function.
There are a couple of problems with how you are using the code in your original example. First of all, you are not passing any constructor arguments to the base constructor. This is a problem because, as you can see in the plain-Thread example, constructor arguments are often necessary. You should rewrite StoppableThread.__init__ as follows:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._stop_event = threading.Event()
Since you are using Python 3, you do not need to provide arguments to super. Now you can do
testthread = StoppableThread(target=funct)
This is still not an optimal solution, because funct uses an external variable, testthread to stop itself. While this is OK-ish for a tiny example like yours, using global variables like that normally causes a huge maintenance burden and you don't want to do it. A much better solution would be to extend the generic StoppableThread class for your particular task, so you can access self properly:
class MyTask(StoppableThread):
def run(self):
while not self.stopped():
time.sleep(1)
print("Hello")
testthread = MyTask()
testthread.start()
time.sleep(5)
testthread.stop()
If you absolutely do not want to extend StoppableThread, you can use the current_thread function in your task in preference to reading a global variable:
def funct():
while not current_thread().stopped():
time.sleep(1)
print("Hello")
testthread = StoppableThread(target=funct)
testthread.start()
sleep(5)
testthread.stop()
I found some implementation of a stoppable thread - and it does not rely that You check if it should continue to run inside the thread - it "injects" an exception into the wrapped function - that will work as long as You dont do something like :
while True:
try:
do something
except:
pass
definitely worth looking at !
see : https://github.com/kata198/func_timeout
maybe I will extend my wrapt_timeout_decorator with such kind of mechanism, which You can find here : https://github.com/bitranox/wrapt_timeout_decorator
Inspired by above solution I created a small library, ants, for this problem.
Example
from ants import worker
#worker
def do_stuff():
...
thread code
...
do_stuff.start()
...
do_stuff.stop()
In above example do_stuff will run in a separate thread being called in a while 1: loop
You can also have triggering events , e.g. in above replace do_stuff.start() with do_stuff.start(lambda: time.sleep(5)) and you will have it trigger every 5:th second
The library is very new and work is ongoing on GitHub https://github.com/fa1k3n/ants.git

An "On Stop" method for python Threads

Me and a friend are having a programming challenge to who can make a good VOS (Virtual Operating System) and currently mine is running custom programs from Threads within the program, I am using Tkinter currently so the separate Threads have their own self.master.mainloop(). I have all the Threads stored in a list but I was wondering whether I could call a function in the Thread which would call a subroutine in the program telling it to do self.master.destroy(). Is there any way to do this?
I would like something along the lines of
class ToBeThread():
def __init__(self):
self.master = Tk()
self.master.mainloop()
def on_stop(self, reason):
self.master.destroy()
Then in my main class
from threading import Thread
thread = Thread(ToBeThread())
thread.setDaemon(True)
thread.on_stop += ToBeThread.on_stop # Similar to how it is done in c#
thread.start()
...
...
thread.stop() # This calls the functions related to the "on_stop"
I have found a way to do this, so for any wondering I did:
from threading import Thread
class MyThread(Thread):def __init__(self, method, delay=-1):
Thread.__init__(self)
self.method = method
self._running = False
self.delay = delay
self.setDaemon(True)
def run(self):
self._running = True
while self._running == True:
self.method()
if self.delay != -1:
time.sleep(self.delay)
def stop(self):
self._running = False
This allows me to write pass a function in through the initialiser, and it will run it ever x seconds or as many times as possible until I do thread.stop()

Python Threading. Why can I only run one thread at a time

I am experimenting with threading for a project I am working on. Here is the code I am using as a test
import threading
class one(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
while 1:
print "one"
class two(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
while 1:
print "two"
threads = []
one = one()
two = two()
one.start()
two.start()
threads.append(one)
threads.append(two)
for t in threads:
t.join()
The problem is that only class one runs. Can you see a problem with my code?
You have to override the run method, not __init__:
class one(threading.Thread):
def run(self):
while 1:
print "one"
This method is what is executed on a different thread, while one = one() starts an infinite loop in the same thread that the object is created.
Override __init__ if you want to pass an argument to be used in the new thread, for instance:
class NumberedThread(threading.Thread):
def __init__(self, number):
threading.Thread.__init__(self)
self.number = number
def run(self):
while 1:
print self.number
NumberedThread("one").start()
NumberedThread("two").start()
You've put an infinite loop in your thread constructor. Your first "thread" never even gets out of its constructor, so the code that's trying to create it just sits and waits for the object to be created. As a result, you aren't actually multithreading anything: you've just got an infinite loop in the main thread.
Override run instead of __init__ and you should be all set.
class one(threading.Thread):
def run(self):
while 1:
print "one"
class two(threading.Thread):
def run(self):
while 1:
print "two"

Resources