Simplest way for PyQT Threading
Solution 1
I believe the best approach is using the signal/slot mechanism. Here is an example. (Note: see the EDIT below that points out a possible weakness in my approach).
from PyQt4 import QtGui
from PyQt4 import QtCore
# Create the class 'Communicate'. The instance
# from this class shall be used later on for the
# signal/slot mechanism.
class Communicate(QtCore.QObject):
myGUI_signal = QtCore.pyqtSignal(str)
''' End class '''
# Define the function 'myThread'. This function is the so-called
# 'target function' when you create and start your new Thread.
# In other words, this is the function that will run in your new thread.
# 'myThread' expects one argument: the callback function name. That should
# be a function inside your GUI.
def myThread(callbackFunc):
# Setup the signal-slot mechanism.
mySrc = Communicate()
mySrc.myGUI_signal.connect(callbackFunc)
# Endless loop. You typically want the thread
# to run forever.
while(True):
# Do something useful here.
msgForGui = 'This is a message to send to the GUI'
mySrc.myGUI_signal.emit(msgForGui)
# So now the 'callbackFunc' is called, and is fed with 'msgForGui'
# as parameter. That is what you want. You just sent a message to
# your GUI application! - Note: I suppose here that 'callbackFunc'
# is one of the functions in your GUI.
# This procedure is thread safe.
''' End while '''
''' End myThread '''
In your GUI application code, you should create the new Thread, give it the right callback function, and make it run.
from PyQt4 import QtGui
from PyQt4 import QtCore
import sys
import os
# This is the main window from my GUI
class CustomMainWindow(QtGui.QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
self.setGeometry(300, 300, 2500, 1500)
self.setWindowTitle("my first window")
# ...
self.startTheThread()
''''''
def theCallbackFunc(self, msg):
print('the thread has sent this message to the GUI:')
print(msg)
print('---------')
''''''
def startTheThread(self):
# Create the new thread. The target function is 'myThread'. The
# function we created in the beginning.
t = threading.Thread(name = 'myThread', target = myThread, args = (self.theCallbackFunc))
t.start()
''''''
''' End CustomMainWindow '''
# This is the startup code.
if __name__== '__main__':
app = QtGui.QApplication(sys.argv)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
''' End Main '''
EDIT
Mr. three_pineapples and Mr. Brendan Abel pointed out a weakness in my approach. Indeed, the approach works fine for this particular case, because you generate / emit the signal directly. When you deal with built-in Qt signals on buttons and widgets, you should take another approach (as specified in the answer of Mr. Brendan Abel).
Mr. three_pineapples adviced me to start a new topic in StackOverflow to make a comparison between the several approaches of thread-safe communication with a GUI. I will dig into the matter, and do that tomorrow :-)
Solution 2
You should use the built in QThread
provided by Qt. You can place your file monitoring code inside a worker class that inherits from QObject
so that it can use the Qt Signal/Slot system to pass messages between threads.
class FileMonitor(QObject):
image_signal = QtCore.pyqtSignal(str)
@QtCore.pyqtSlot()
def monitor_images(self):
# I'm guessing this is an infinite while loop that monitors files
while True:
if file_has_changed:
self.image_signal.emit('/path/to/image/file.jpg')
class MyWidget(QtGui.QWidget):
def __init__(self, ...)
...
self.file_monitor = FileMonitor()
self.thread = QtCore.QThread(self)
self.file_monitor.image_signal.connect(self.image_callback)
self.file_monitor.moveToThread(self.thread)
self.thread.started.connect(self.file_monitor.monitor_images)
self.thread.start()
@QtCore.pyqtSlot(str)
def image_callback(self, filepath):
pixmap = QtGui.QPixmap(filepath)
...
user3696412
Updated on July 10, 2022Comments
-
user3696412 almost 2 years
I have a GUI in PyQt with a function
addImage(image_path)
. Easy to imagine, it is called when a new image should be added into a QListWidget. For the detection of new images in a folder, I use athreading.Thread
withwatchdog
to detect file changes in the folder, and this thread then callsaddImage
directly.This yields the warning that
QPixmap
shouldn't be called outside the gui thread, for reasons of thread safety.What is the best and most simple way to make this threadsafe? QThread? Signal / Slot? QMetaObject.invokeMethod? I only need to pass a string from the thread to
addImage
. -
K.Mulier about 8 yearsI ran into this problem just today. Me too, I accessed my GUI in a thread-unsafe way. My application crashed miserably, without proper error reports: stackoverflow.com/questions/37242849/… Then I used the signal/slot mechanism as I described in my answer. It helped me out. I'm happy it helps you too :-)
-
K.Mulier about 8 yearsWho downvoted this answer, without leaving a comment? :-( Please just leave a comment to explain why this answer is not good, before making the downvote.
-
user3696412 about 8 yearsIsn't this basically the same as the above answer, just using
QThread
instead ofthreading.Thread
? Seems to be nearly the same code. -
K.Mulier about 8 yearsI am a reasonable person. Anyone who feels that my answer is not good, can just tell me why, and I will do my best to make the proper changes. But just downvoting without any explanation doesn't help anyone. Please..
-
Brendan Abel about 8 yearsThe difference is that this is the canonical way to use threads in Qt/PyQt -- my example is basically the same as the Worker Model described in the official documentation. It's also generally discouraged to emit signals on other objects from outside those objects, which the other answer does and which the official worker model does not.
-
K.Mulier about 8 yearsHi Brendan @Brendan Abel, Can you clarify: "It's also generally discouraged to emit signals on other objects from outside those objects, which the other answer does and which the official worker model does not"? I'm interested to know more about this. Because I use the signal / slot mechanism a lot in my most recent application. Thank you very much :-)
-
Brendan Abel about 8 yearsInternally, Qt Signals are implemented as protected member functions. So while what you did may work in this example (because the signal was created in python), it doesn't work as a general solution (ie. it won't generally work with the builtin Qt signals on buttons and widgets).
-
K.Mulier about 8 yearsWhat is a "protected member function"?
-
K.Mulier about 8 yearsSo my approach will work as long as Python creates the signal?
-
K.Mulier about 8 yearsI'm trying to figure out what your code is doing. When you write
self.thread = QtCore.QThread(self)
you make a special kind of thread - a QThread - which is somehow connected to theself
- the QWidget in this case. Then you select the function that the thread should execute when it is started, throughself.thread.started.connect(self.file_monitor.monitor_images)
. Am I correct? I'm a bit confused, because I am so used to working with 'normal' threads, not QThreads. Your answer is really interesting. Thank you so much :-) -
three_pineapples about 8 years@K.Mulier I'm not the person who down voted, but I suspect the down vote was because this goes against the consensus of this question, which is to use Qt threads. While it currently works with Python threads (I've personally done similar things with Python threads many times with no issues), there are no guarantees that it will not break with a future version of PyQt/Python.
-
three_pineapples about 8 years@K.Mulier The
self
when constructing theQThread
is simply indicating the parent, it's not really important. The important line is the one withmoveToThread()
which ensures that theQObject
subclass instance lives in the correct thread. TheQThread
object has an event loop, which then executes themonitor_images
slot. A better example would probably be to replace thewhile True
loop with aQTimer
so that theQThread
event loop could respond to other signals/slots (at the moment it is always stuck executing themonitor_images
slot) -
three_pineapples about 8 years@K.Mulier You might also consider asking a new question on SO about this if you want a more detailed answer (comments are not exactly the best place for detailed explanations)
-
K.Mulier about 8 yearsThank you mr. three_pineapples. Your comment is really interesting. I will follow your advice to start a new topic on StackOverflow about the subject. Thank you for your help :-)