Simplest way for PyQT Threading

28,215

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)
        ...
Share:
28,215
user3696412
Author by

user3696412

Updated on July 10, 2022

Comments

  • user3696412
    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 a threading.Thread with watchdog to detect file changes in the folder, and this thread then calls addImage 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
    K.Mulier about 8 years
    I 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
    K.Mulier about 8 years
    Who 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
    user3696412 about 8 years
    Isn't this basically the same as the above answer, just using QThread instead of threading.Thread? Seems to be nearly the same code.
  • K.Mulier
    K.Mulier about 8 years
    I 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
    Brendan Abel about 8 years
    The 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
    K.Mulier about 8 years
    Hi 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
    Brendan Abel about 8 years
    Internally, 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
    K.Mulier about 8 years
    What is a "protected member function"?
  • K.Mulier
    K.Mulier about 8 years
    So my approach will work as long as Python creates the signal?
  • K.Mulier
    K.Mulier about 8 years
    I'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 the self - the QWidget in this case. Then you select the function that the thread should execute when it is started, through self.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
    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
    three_pineapples about 8 years
    @K.Mulier The self when constructing the QThread is simply indicating the parent, it's not really important. The important line is the one with moveToThread() which ensures that the QObject subclass instance lives in the correct thread. The QThread object has an event loop, which then executes the monitor_images slot. A better example would probably be to replace the while True loop with a QTimer so that the QThread event loop could respond to other signals/slots (at the moment it is always stuck executing the monitor_images slot)
  • three_pineapples
    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
    K.Mulier about 8 years
    Thank 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 :-)