I'm trying to figure out how to interact with the main thread in PyQt5 from another thread running in another file.
In the example below I'm trying to make a button green from the followup.py file's initialize function which is a thread started in the workbot_main.py file.
From the followup.py file I can't call 'workbot_main.widget' because it doesn't recognize it and I can't call workbot_main.MainWindow because it doesn't recognize it as an instantiated class so 'self' doesn't work and therefore most things within the class.
How am I supposed to interact with the MainWindow thread from another file?
I tried using slots and signals but I can't get that to work either.
Help would be massively appreciated.
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
followup_start_button = self.ui.followup_start_button
followup_start_button.clicked.connect(threads.thread_launch_followup_initialize)
p1_followup_button = self.ui.p1_followup_button
#QtCore.pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
class Threads:
def __init__(self):
pass
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target = followup.initialize, args = ())
t1.start()
threads = Threads()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
#followup.py file
def make_green():
workbot_main.MainWindow.p1_followup_button_color_green()
initialize():
make_green()
do other stuff
Edit:
I've tried doing a great many things, the only thing I could get to work is this
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
class Change_green(QObject):
setgreen = pyqtSignal()
#pyqtSlot()
def green(self):
self.setgreen.emit()
print("clicked")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.Change_green = Change_green()
self.Change_green.setgreen.connect(lambda: print("connected"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
followup_start_button = self.ui.followup_start_button
followup_start_button.clicked.connect(self.Change_green.green) # <--- this works
p1_followup_button = self.ui.p1_followup_button
#pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
But that is useless to me since the signal is sent from inside the main thread.
What I need to work is this
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
class Change_green(QObject):
setgreen = pyqtSignal()
#pyqtSlot()
def green(self):
self.setgreen.emit()
print("clicked")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.Change_green = Change_green()
self.Change_green.setgreen.connect(lambda: print("connected"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
followup_start_button = self.ui.followup_start_button
#starts the initialize function in followup.py which calls the Change_green class above
followup_start_button.clicked.connect(threads.thread_launch_followup_initialize)
p1_followup_button = self.ui.p1_followup_button
#pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
class Threads:
def __init__(self):
pass
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target = followup.initialize, args = ())
t1.start()
threads = Threads()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
# followup.py
import workbot_main
color = workbot_main.Change_green()
def initialize():
print("initializing")
color.green()
But if I call the Change_green() function from followup.py, the Change_green() function gets called properly because "clicked" gets printed but there is no following "connected" being printed from the main thread.
It's as if self.setgreen.emit() only works when called from within the main thread.
I couldn't execute your script since you used a QTDesigner project to create the GUI. Also, I don't know which modules are workbot or followup, so I didn't tried to install them as they might be private modules.
I can see that you used the threading module from python. In many tutorials available on the internet, they tell you to not use this module when dealing with Qt. If need multithreading, use the QThread class instead.
The reason is that Qt schedules its own events. When dealing with raw data, that might be ok. But when dealing with QWidgets, their methods are not thread safe, so executing them from another thread might lead you to many undesired problems.
As I recommend using QThreads, I provided a minor example below explaining how to use it (it's not so easy to start using QThreads, I had and still have a hard time when dealing with them).
If you need, you can place the QThread and QObject classes in another file, then import them from main file. There's no problem about that, just be careful to not let their instances' references be collected by the python's garbage collector.
I keep them out of reach by using self.thread = ChangeColorThread(...) inside the Scene's constructor. In this way, the thread's instance is bound to the Scene, and it's reference will not go out of scope when the Scene's constructor block ends.
from PySide2.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
from PySide2.QtCore import QObject, QThread, Signal, QMutex
import random
# I use PySide2 which is the same thing as PyQt5 with the exception of a few variable names
# that might change from one module to another. One of them might be `Signal` (PySide2) to
# `pyqtSignal` (PyQt5) for example.
#
# The imports should also change:
# from PySide2.QtWidgets import ...
# to
# from PyQt5.QtWidgets import ...
#
# But the idea is the same for both modules.
# -------------------------------------------------------------------------------------
# After a few researches on the Multi-Threading topic, I liked the option of using the
# `QObject.moveToThread(thread)` method to create threads based on QObjects.
#
# There are others:
# - Using QThreadPool and QRunnable
# - Using QThread only (by overwriting run() method. Not recommended.)
#
# For me, I got everything working so far using the `moveToThread` method, so that's
# the one I will be using on this example.
# -------------------------------------------------------------------------------------
# Here we create the Worker Object. Everything inside `Worker.main(self)` will be
# executed in another thread: ChangeColorThread.
#
# As we might inspect / change the value of `Worker.running` on both threads at the same time
# (Main Thread and ChangeColorThread), I recommend using a QMutex to restrict access
# to this variable.
class Worker(QObject):
# Note the signal possesses a `object` as a parameter. It signalizes the QObject
# that you want to pass an object type to the signal's receiver.
#
# In this case, it's a tuple (red, green, blue) as `Scene.setRandomColor` has the
# `color` parameter.
produce = Signal(object)
# Called from main thread only to construct the Worker instance.
def __init__(self, delay):
QObject.__init__(self)
self.running = True
self.delay = delay
self.runningLock = QMutex()
# Might be called from both threads.
def stop(self):
self.runningLock.lock()
self.running = False
self.runningLock.unlock()
# Might be called from both threads.
def stillRunning(self):
self.runningLock.lock()
value = self.running
self.runningLock.unlock()
return value
# Executes on ChangeColorThread only.
def main(self):
while (self.stillRunning()):
# Generate the random colors from the ChangeColorThread.
red = random.randint(0, 255)
green = random.randint(0, 255)
blue = random.randint(0, 255)
# Emit the `produce` signal to safely call the `Scene.setRandomColor()`
# on the Main Thread.
#
# In order to update any GUI or call any QWidget method, you must emit
# a connected signal to the main thread, so you don't raise any exceptions
# or segmentation faults (or worse, silent crashes).
self.produce.emit((red, green, blue))
# Tell the ChangeColorThread to sleep for delay microseconds
# (aka a value of 1000 == 1 second)
self.thread().msleep(self.delay)
print('Quit from ChangeColorThread Worker.main()')
self.thread().quit()
# Here we have the other thread we will instantiate. The thread by itself
# is nothing special. It acts like a QObject until `QThread.start()` is
# called. It also has a few important properties you might want to take a look:
# - started (calls one or more connected functions once the thread starts executing).
# - finished (calls one or more connected functions once the thread properly finishes).
#
# When `start()` is called, as we connected `self.started` to `Worker.main`,
# `Worker.main` will start executing on the other thread until a `stop` call
# is requested.
class ChangeColorThread(QThread):
def __init__(self, produce_callback, delay=100):
QThread.__init__(self)
self.worker = Worker(delay)
self.worker.moveToThread(self)
self.started.connect(self.worker.main)
self.worker.produce.connect(produce_callback)
# Stops the worker object's main loop from the main thread.
def stop(self):
self.worker.stop()
# Here we will create the GUI. I kept it simple, it contains a single QLabel.
#
# It also starts the ChangeColorThread thread.
class Scene(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
self.label = QLabel("<h3>See the Color Change?</h3>")
layout.addWidget(self.label)
self.thread = ChangeColorThread(self.setRandomColor, 500)
self.thread.start()
# This function will execute on the Main Thread only.
#
# However, this function is not called by the user. It is scheduled to execute at some point
# by PyQt5 once `Worker.produce` is emitted.
def setRandomColor(self, color):
self.label.setStyleSheet("background-color:rgb(%d,%d,%d)" % (color[0], color[1], color[2]))
# As we don't know wether ChangeColorThread has stopped or not, we signalize
# it to stop before closing the application.
#
# This step is important, because even if PyQt5 / PySide2 tries its best to close the
# remaining thread, once the window is closed, this process can fail. The result is a
# hidden process still running even after the program is closed.
def sceneInterceptCloseEvent(self, evt):
self.thread.stop()
self.thread.wait()
evt.accept()
# Here we create the Main Window. It contains a single scene with the QLabel inside it.
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.scene = Scene()
self.setCentralWidget(self.scene)
# We call Scene.sceneInterceptCloseEvent in order to signalize the running thread
# to stop its main loop before the application is closed.
def closeEvent(self, evt):
self.scene.sceneInterceptCloseEvent(evt)
if __name__ == '__main__':
app = QApplication()
win = Window()
win.show()
app.exec_()
So the idea of this example is simple. We have 2 threads:
The Main Thread: which handles the GUI, scene creation, user input, and thread management. Whenever the user tries to exit the application, it's on this thread that the other ones will be signalized to be stopped.
ChangeColorThread: which is an infinite loop that executes on the background, to change the QLabel's color at every few seconds, set by the delay variable. It can be signalized to stop by the Main Thread.
Once the application starts executing, the label's background color should change based on the emission of Worker.produce signal, which sets which random color should be used for the label.
You can also move the window or resize it. By doing these manual operations, it proves that the ChangeColorThread is really executing at the background of the application. Otherwise the application would become unresponsive (frozen).
As stated on the script, I used PySide2 instead of PyQt5. But its almost the same thing, as just a few variable names should change from one to another. If you find any problems because of this, just let me know on the comments.
Edit: How to make your script work with both ways
What confused me about your post was that you rewrote your workbot_main.py script 3x, then wrote followup.py 2x, one at the middle and one at the end. I was not understanding your examples. It's hard to keep track of a lot of code, specially if you provide a whole script when you just change one line or another.
After checking them with more paciency, I was able to understand what you wanted to do. I think...
You're trying to this right?
You click on self.ui.followup_start_button
This button is connected to threads.thread_launch_followup_initialize
Which calls Change_green.green()
Which emits the signal setgreen
setgreen is connected to MainWindow.p1_followup_button_color_green
This function on MainThread changes the background color of self.ui.p1_followup_button to the green color.
The main problem I found with your script is that you're creating two different instances of Change_green:
First one: in your followup.py file.
Second one: inside your MainWindow.__init__(self).
So everytime that you tried to access the object created on MainWindow from your thread, you were in fact accessing a different instance of the same class. After solving this problem and making a few tweaks on your script, it worked without any errors so far. (Yes, the button's background color did changed to green.)
Also, you say that you can't solve your problem using QThreads. In the final example below, I also use your own classes (with one additional method and Signal) and a custom QThread I created, to reproduce the same type of idea, but now changing the button color to red.
I put all Threading Classes into followup.py, and left only Scene and Logic Classes on workbot_main.py.
Here is your new workbot_main.py:
# from workbot import * # Can't import from workbot as I don't have this module on my side
import followup
from PySide2 import QtWidgets
from PySide2 import QtCore
from PySide2.QtWidgets import QMainWindow, QApplication, QWidget, QPushButton, QVBoxLayout
from PySide2.QtCore import QThread, Signal, QObject, Slot, Qt
class Ui(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
self.followup_start_button = QPushButton("Start FollowUp (Green)")
self.qthread_button = QPushButton("Start QThread (Red)")
self.p1_followup_button = QPushButton("Check me Turn Out Green or Red")
layout.addWidget(self.followup_start_button)
layout.addWidget(self.qthread_button)
layout.addWidget(self.p1_followup_button)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# --------------------------------------------
# # As I don't have your QTDesigner file, I cannot
# # load it.
# # UI_MainWindow is from the workbot.py file which is generated from QTDesigner:
# self.ui = Ui_MainWindow()
# self.ui.setupUi(self)
# --------------------------------------------
# So I created my own "UI" to load both buttons on the screen
# and execute your script.
self.ui = Ui()
self.setCentralWidget(self.ui)
# --------------------------------------------
# threading module (green)
# --------------------------------------------
self.Change_green = followup.Change_green()
self.Change_green.setgreen.connect(lambda: print("connected green"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
# Starts the initialize function in followup.py which calls the Change_green class above
#
# After creating the Change_green instance, we initialize the thread
# from another file here, and pass the instance to the thread, so it
# can access it from there.
self.threads = followup.Threads(self.Change_green)
self.followup_start_button = self.ui.followup_start_button
self.followup_start_button.clicked.connect(self.threads.thread_launch_followup_initialize)
# This line was wrong. But you probably meant to assign this variable to
# your class. After fixing it your script does not raises any error.
self.p1_followup_button = self.ui.p1_followup_button
# --------------------------------------------
# QThread class (red)
# --------------------------------------------
# With QThread you solve this in one line given that everying is configured
# on MyThread(QThread) class.
self.qthread = followup.MyThread(self.Change_green, self.qthread_button_color_red)
self.ui.qthread_button.clicked.connect(self.qthread.start)
# I Changed pyqtSlot -> Slot (As I use PySide2 instead of PyQt5)
#Slot()
def p1_followup_button_color_green(self):
self.p1_followup_button.setStyleSheet("background-color: green")
# I Changed pyqtSlot -> Slot (As I use PySide2 instead of PyQt5)
#Slot()
def qthread_button_color_red(self):
self.p1_followup_button.setStyleSheet("background-color: red")
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
Here is your new followup.py:
from PySide2.QtCore import QObject, QThread, Signal, Slot
import threading
# ------------------------------------------------------------
# - Fixing your script to use threading module
# ------------------------------------------------------------
# We don't need to import workbot_main anymore.
#
# `color` is the same object being referenced from `MainWindow.Change_green`,
# which was passed by parameter when the thread was initialized on
# `MainWindow.__init__(self)`.
#
# Now the code should work as expected.
def initialize(color):
print("initializing: ", color)
color.green()
# To make the example simpler, I've put all threading scripts here on followup.py
class Threads:
def __init__(self, color):
self.color = color
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target=initialize, args=[self.color])
t1.start()
# Including this object here which will run in another thread.
#
# As I use PySide2 I changed
# "pyqtSignal" -> "Signal"
# "pyqtSlot" -> "Slot"
class Change_green(QObject):
setgreen = Signal()
setred = Signal()
#Slot()
def green(self):
self.setgreen.emit()
print("clicked on green")
#Slot()
def red(self):
self.setred.emit()
print("clicked on red")
self.thread().quit()
# ------------------------------------------------------------
# - Using QThreads instead of python threading module
# ------------------------------------------------------------
class MyThread(QThread):
def __init__(self, change_green, qthread_button_color_red):
QThread.__init__(self)
self.Change_green = change_green
self.Change_green.setred.connect(lambda: print("connected red"))
self.Change_green.setred.connect(qthread_button_color_red)
# This is important. It sets the QObject to run on another thread.
self.Change_green.moveToThread(self)
# Set Change_green.red method to be runned on the other thread
self.started.connect(self.Change_green.red)
# Set the main thread to wait for the other thread, once this
# even is fired.
self.Change_green.setred.connect(self.wait)
Again, I still use PySide2 instead of PyQt5, so instead of using pyqtSignal and pyqtSlot, I used Signal and Slot. This change also happened on the module imports.
I left the QThread example here (Original Answer before the edit) in case you want to compare both of them.
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
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
I wrote simple program which has pyQt interface with 2 buttons (start and cancel). Start button runs some calculations in the background (by starting update function) and thanks to threading I can still use UI.
But the application crashes after 10sec - 2 minutes. UI just dissapears, program shutdown.
when I use pythonw to run app without console thread crashes after ~25 sec but gui still works.
#!/usr/bin/python
import threading
import sys
from PyQt4 import QtGui, QtCore
import time
import os
class Class(QtGui.QWidget):
def __init__(self):
#Some init variables
self.initUI()
def initUI(self):
#some UI
self.show()
def update(self,stop_event):
while True and not stop_event.isSet():
self.updateSpeed()
self.updateDistance()
self.printLogs()
self.saveCSV()
self.guiUpdate()
time.sleep(1)
#gui button function
def initiate(self):
self.stop_event = threading.Event()
self.c_thread = threading.Thread(target = self.update, args=(self.stop_event,))
self.c_thread.start()
#Also gui button function
def cancelTracking(self):
self.stop_event.set()
self.close()
def main():
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
ex.update()
if __name__ == '__main__':
main()
I dont know if I'm doing threading right. I found example like this on stack. I'm quite new to python and I'm using threading for the first time.
It is most likely due to calling a GUI function in your separate thread. PyQt GUI calls like setText() on a QLineEdit are not allowed from a thread. Anything that has PyQt painting outside of the main thread will not work. One way to get around this is to have your thread emit a signal to update the GUI when data is ready. The other way is to have a timer periodically checking for new data and updating the paintEvent after a certain time.
========== EDIT ==========
To Fix this issue I created a library named qt_thread_updater. https://github.com/justengel/qt_thread_updater This works by continuously running a QTimer. When you call call_latest the QTimer will run the function in the main thread.
from qt_thread_updater import get_updater
lbl = QtWidgets.QLabel('Value: 1')
counter = {'a': 1}
def run_thread():
while True:
text = 'Value: {}'.format(counter['a'])
get_updater().call_latest(lbl.setText, text)
counter['a'] += 1
time.sleep(0.1)
th = threading.Thread(target=run_thread)
th.start()
========== END EDIT ==========
#!/usr/bin/python
import threading
import sys
from PyQt4 import QtGui, QtCore
import time
import os
class Class(QtGui.QWidget):
display_update = QtCore.pyqtSignal() # ADDED
def __init__(self):
#Some init variables
self.initUI()
def initUI(self):
#some UI
self.display_update.connect(self.guiUpdate) # ADDED
self.show()
def update(self):
while True and not self.stop_event.isSet():
self.updateSpeed()
self.updateDistance()
self.printLogs()
self.saveCSV()
# self.guiUpdate()
self.display_update.emit() # ADDED
time.sleep(1)
#gui button function
def initiate(self):
self.stop_event = threading.Event()
self.c_thread = threading.Thread(target = self.update)
self.c_thread.start()
#Also gui button function
def cancelTracking(self):
self.stop_event.set()
self.close()
def main():
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
# ex.update() # - this does nothing
if __name__ == '__main__':
main()
The other thing that could be happening is deadlock from two threads trying to access the same variable. I've read that this shouldn't be possible in python, but I have experienced it from the combination of PySide and other Python C extension libraries.
May also want to join the thread on close or use the QtGui.QApplication.aboutToQuit signal to join the thread before the program closes.
The Qt documentation for QThreads provides two popular patterns for using threading. You can either subclass QThread (the old way), or you can use the Worker Model, where you create a custom QObject with your worker functions and run them in a separate QThread.
In either case, you can't directly update the GUI from the background thread, so in your update function, the guiUpdate call will most likely crash Qt if it tries to change any of the GUI elements.
The proper way to run background processes is to use one of the two QThread patterns and communicate with the main GUI thread via Signals and Slots.
Also, in the following bit of code,
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
ex.update()
app.exec_ starts the event loop and will block until Qt exits. Python won't run the ex.update() command until Qt has exited and the ex window has already been deleted, so you should just delete that command.
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()