1

1. Some background info

The official documentation on how to instantiate and use a QThread can be found here: http://doc.qt.io/qt-5/qthread.html

The documentation describes two basic approaches: (1) worker-object approach and (2) QThread subclass approach.

    > The following article explains why one needs to be careful when using the second approach: https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

    > The following article explains why both approaches have their merits: https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html

I quote:

As a rule of thumb:

  • If you do not really need an event loop in the thread, you should subclass.
  • If you need an event loop and handle signals and slots within the thread, you may not need to subclass.
  • As I do need some sort of communication between the QApplication thread and the new QThread (and I believe signal-slot is a good way to communicate), I'll use the worker-object approach.

     

    2. Sample code from the official docs

    On the official Qt5 docs on QThreads (see http://doc.qt.io/qt-5/qthread.html), you can find sample code. I've done an effort to translate it to Python:
    (See this stackoverflow question to see more details on that translation: QThreads in Pyqt5: is this the correct C++ to Python translation of the official QThread docs?)

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    
    class Worker(QObject):
    
        resultReady = pyqtSignal(str)
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # Note: this constructor is empty now.
            # But would it be okay to instantiate new
            # objects here, and use them in doWork(..)?
    
        # A more general question: is it okay if
        # doWork(..) accesses variables that were
        # created in another thread (perhaps the
        # main QApplication thread)?
    
        @pyqtSlot(str)
        def doWork(self, param):
            result = "hello world"
            print("foo bar")
            # ...here is the expensive or blocking operation... #
            self.resultReady.emit(result)
    
    
    class Controller(QObject):
    
        operate = pyqtSignal(str)
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            # 1. Create 'workerThread' and 'worker' objects
            # ----------------------------------------------
            self.workerThread = QThread()
            self.worker = Worker()
            self.worker.moveToThread(self.workerThread)
    
            # 2. Connect all relevant signals
            # --------------------------------
            self.workerThread.finished.connect(self.worker.deleteLater)
            self.operate.connect(self.worker.doWork)
            self.worker.resultReady.connect(self.handleResults)
    
            # 3. Start the thread
            # --------------------
            self.workerThread.start()
    
        def __del__(self):
            self.workerThread.quit()
            self.workerThread.wait()
    
        @pyqtSlot(str)
        def handleResults(self, param):
            print(param)
            global app
            app.exit()
    
    
    if __name__ == '__main__':
        app = QCoreApplication([])
        controller = Controller()
        controller.operate.emit("foo")
        sys.exit(app.exec_())
    

    As you can see, the constructor of the Worker class is empty. That brings us to the next paragraph.

     

    3. My question: okay to create objects in the worker's constructor?

    From the great article of Mss. Maya Posch (https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/), I quote:

    By the way, one extremely important thing to note here is that you should NEVER allocate heap objects (using new) in the constructor of the QObject class as this allocation is then performed on the main thread and not on the new QThread instance, meaning that the newly created object is then owned by the main thread and not the QThread instance. This will make your code fail to work. Instead, allocate such resources in the main function slot such as process() in this case as when that is called the object will be on the new thread instance and thus it will own the resource.
    [Maya Posch - how to really truly use qthreads, the full explanation]

    This article was written for C++ software.

    Does this quote still apply on Python applications? In other words, is it okay to instantiate objects in the constructor of the Worker class?

    More generally, I could ask: is it okay if the doWork(..) function accesses variables that were instantiated in another thread (perhaps the main QApplication thread)?


    Hereby I provide my current system settings:
        >  Qt5 (QT_VERSION_STR = 5.10.1)
        >  PyQt5 (PYQT_VERSION_STR = 5.10.1)
        >  Python 3.6.3
        >  Windows 10, 64-bit

    K.Mulier
    • 8,069
    • 15
    • 79
    • 141
    • going to the point, an object belongs to the thread in which it was created, in the case of Qt it avoids that an object belonging to one thread is used in another, so the correct thing is not to create it in the constructor, or to do it and then move it to the new thread with moveToThread(). Instead of looking at the language, look at the basic concepts offered by Qt. – eyllanesc May 25 '18 at 19:56
    • In addition, the code it provides has nothing to do with your current question, you do not see the creation of any object in the constructor, it does not indicate that you have a problem. – eyllanesc May 25 '18 at 19:58
    • Translating code from one language to another does not make much sense if you only rely on languages, since a library like Qt offers other restrictions. – eyllanesc May 25 '18 at 20:00
    • Hi @eyllanesc, you're right. The constructor of the `Worker` class is currently empty. I've added a comment in the code to clarify :-) – K.Mulier May 25 '18 at 20:00
    • Hi @eyllanesc, you're right about the translation issues. Please have a look at my other StackOverflow question that focuses on the translation itself: https://stackoverflow.com/questions/50531797/qthreads-in-pyqt5-is-this-the-correct-c-to-python-translation-of-the-official . Feel free to post there your remarks :-) – K.Mulier May 25 '18 at 20:01
    • One makes a variable a member of the class if it wants to have the same scope as the class, in your case you just indicate that you want to do a task in doWork (), so for me there is no need to create it in the constructor. What advantages would it have to do in the constructor? – eyllanesc May 25 '18 at 20:02
    • You're right. But I just want to understand what is going on in the background. Perhaps I could put the question a bit more general: is it okay if the `doWork()` function accesses variables that were instantiated in the main QApplication thread? – K.Mulier May 25 '18 at 20:05
    • You should not access objects that belong to another thread if you are going to modify it, one thing is reading and another is writing. For that, Qt offers the signals and the QMetaObject. – eyllanesc May 25 '18 at 20:06
    • To summarize: a basic rule do not modify an object in another thread to which it does not belong, when I refer to an object I refer to a QObject. – eyllanesc May 25 '18 at 20:08
    • If you want a more detailed explanation of what I indicated you could read https://doc.qt.io/archives/qt-5.10/thread-basics.html – eyllanesc May 25 '18 at 20:09
    • Thank you @eyllanesc, I will certainly read that! – K.Mulier May 25 '18 at 20:10
    • Also, many times they confuse the concept of thread with QThread, QThread is a handle of a native thread. the native thread is executed in run(), and if you use moveToThread you are moving that object to run(), so the constructor is not being executed in the native thread. I think that your ignorance about it is the cause of your questions. – eyllanesc May 25 '18 at 20:13
    • Hi @eyllanesc , I've given you two or three coffee's for your help (depending on current coffee prices) ;-) – K.Mulier May 25 '18 at 20:13
    • Hi @eyllanesc, that is indeed very interesting. I didn't know that about QThreads. I thought is was somehow a wrapper around native Python threads. Apparently I was wrong. Perhaps you can formulate all these things in an answer? That would be great ;-) A well written answer will perhaps spawn some more coffee's ;-) – K.Mulier May 25 '18 at 20:15
    • I will try, in a few minutes I will publish my answer. – eyllanesc May 25 '18 at 20:16
    • Great. Please take your time. I will now leave the computer. Tomorrow I come back to read your answer and send you some coffee. – K.Mulier May 25 '18 at 20:17

    1 Answers1

    0

    In this answer I will sort all the tasks of my comments.

    Many times he confuses the concept of thread with QThread, if the docs are revised:

    The QThread class provides a platform-independent way to manage threads

    It is not a wrapper of the native threads but as it is read it is a handler.

    The method run() is the beginning of the thread as also indicated by the docs:

    The starting point for the thread ...

    When you use moveToThread() it changes the thread affinity of the object and its children, this refers to the QObjects.

    For what your citation indicates you should avoid that a QObject is created in the constructor if you only use it in the doWork(), besides that would imply that it is a member of the class that is often unnecessary. If you do the solution is that you use moveToThread(), in case it is a child of the Worker is obviously not necessary.


    Qt to avoid these problems of access to objects created in another thread I recommend using the signals or the QMetaObject that are thread-safe.

    Finally the use of QThread is very low level, to do multi-threading tasks Qt offers other technologies with QThreadPool with QRunnable avoiding those problems. In c ++ there is also QtConcurrent but it is not available in PyQt

    eyllanesc
    • 235,170
    • 19
    • 170
    • 241
    • Hi @eyllanesc, are you sure about the difference between `QThread` and the usual Python thread? If I remember well, you told me that a `QThread` is in fact making use of precompiled Qt code, such that it is not submitted to the Python GIL. This causes the use of `QThread` to be more powerfull, but also more dangerous. However, I have found two links that contradict your claims: https://stackoverflow.com/questions/1595649/threading-in-a-pyqt-application-use-qt-threads-or-python-threads and https://www.mail-archive.com/pyqt@riverbankcomputing.com/msg16052.html – K.Mulier May 26 '18 at 10:13
    • @K.Mulier That of wrapper depends on whether you define it strictly or not, obviously QThread to handle the native threads you should use them, so that perspective is a wrapper. The intention of these post is to make known that Qt uses the native threads and does not create another type of threads. – eyllanesc May 26 '18 at 16:42