pythonmultithreadingpyqt4python-multiprocessingqapplication

QApplication in a non-main thread of second process with pyQt4: is this code legal, and if not, why does it work?


My collaborators and I have programed an integrated development environment (IDE) in python 2.7 and pyQt4 that has been working perfectly for more than ten years. I have now to port it to pyqt5 and I encounter several problems. This post is related to my first problem. Before declaring it as a duplicate, read carrefully the questions at the bottom. This is because I don't understand the answers in the related posts that I ask here other precise questions.

The architecture of my IDE is the following:

Here is the code used for that, which works perfectly. (unfortunately I cannot paste here a stand-alone code of reasonable size, but it should be sufficient to understand and answer the questions)

"""
This model helps to execute GUI code in a QT thread.
"""    
import time, sys
from threading import Thread 
from PyQt4.QtCore import *
from PyQt4.QtGui import *

#from pyview.identifyThreads import identify

signalConnected = False


def _runGuiCodeSignal(f):
        f()

def execInGui(f):
    _ensureGuiThreadIsRunning()
    global app
    if app == None:
        raise Exception("Invalid application handle!")
    app.emit(SIGNAL("runGuiCode(PyQt_PyObject)"), f)

def _createApplication():
    #from pyview.identifyThreads import identify
    global app
    global signalConnected
    global _runGuiCodeSignal
    app = QApplication(sys.argv)          #
    app.setQuitOnLastWindowClosed(False)  #
    app.connect(app, SIGNAL("runGuiCode(PyQt_PyObject)"), _runGuiCodeSignal, Qt.QueuedConnection | Qt.UniqueConnection)
    signalConnected = True
    if app.thread() != QThread.currentThread():
        raise Exception("Cannot start QT application from side thread! You will have to restart your process...")
    #identify(message='in createApplication just before app.exec')
    app.exec_()


def _ensureGuiThreadIsRunning():
    #from pyview.identifyThreads import identify
    global app
    global signalConnected
    global _runGuiCodeSignal
    app = QApplication.instance()
    if app == None:
       thread = Thread(target=_createApplication)
       #identify('in _ensureGuiThreadIsRunning')
       print "Creating new Qt application in thread ", thread
       thread.daemon = True
       thread.start()
       while thread.is_alive() and (app == None or app.startingUp()):
           time.sleep(0.01)
   else:
       if not signalConnected:
       print app.connect(app, SIGNAL("runGuiCode(PyQt_PyObject)"), _runGuiCodeSignal,
                                  Qt.QueuedConnection | Qt.UniqueConnection)
       signalConnected = True

When defining a function f that creates a QWindow with various widgets, and running execinGUI(f) in ANY thread of P2, the QApplication is created in a new other non-main thread or retrieved if it already exists (like this there is only one QThread in process P2), and everything works perfectly.

Now my questions are:

  1. Is the code above violating any Qt4 rule because it starts a QApplication in a non-main thread?

  2. Which page of the QT4 documentation tells that it is forbidden to process like we have done?

  3. What is the definition of the main thread of a process? The thread that runs the process.run() function?

  4. What is a Qt main thread? Is it by definition the main thread of the process or can it be another thread?

  5. Why don't we have an error "QApplication was not created in the main thread"?

  6. How does Qt4 recognize whether the event loop is running or not in the main thread of a Process?


Solution

  • The concept of main thread is not clearly defined in Qt documentation. Actually, the main thread of a process (process that executes the Process.run function) can be different from the main Qt thread (thread that instantiates the first Qt object like a QApplication), although both "main" threads are often the same one.

    Example of valid code structure:

    function below will run in the process' non-main thread 'thread-1', that will become immediately Qt's main thread.

    def startThread1():      
        app = QApplication(sys.argv)
        app.exec_()  # enter event loop
    

    code below run in process' main thread, not to be confused with the main Qt and unique GUI thread of the process.

    thread1 = Thread(target=self.startThread1)
    thread1.start()
    input('I am busy until you press enter')