-1

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:

  • The main IDE application is a QApplication running in a first process (say P1)
  • A second process (say P2) is created by P1. The main thread of P2 runs an infinite loop, waiting for pieces of code from P1 (through a queue) to be executed in different non-main threads of P2.

  • Although most of the pieces of code to be run are non-gui, one (and only one) of the non-main thread of P2 is a Qt Thread running a QApplication that can run Qt pieces of code.

  • This QApplication (say app) has a normal event loop started with app.exec_(), but can also receive a signal with a gui function to be run as the parameter of the corresponding slot.

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?

user3650925
  • 173
  • 1
  • 10
  • I have taken the time to format your question, please do not destroy the format. On the other hand it provides a [MRE] – eyllanesc Feb 16 '20 at 23:50
  • Thanks eyllanesc. Unfortunately, my code does not provide a stand alone reproducible example (two processes, several queues, a first event loop in main thread of process 2, the code to create the GUI,... all this would be too much). But my questions are about definitions, rules and documentation. – user3650925 Feb 16 '20 at 23:57
  • Your question is not about definitions, rules and documentation in the abstract sense, but clearly you frame it in the code you provide for what the MRE is necessary in this case so if you do not provide it, I will vote to close your question – eyllanesc Feb 17 '20 at 00:01
  • On the other hand, the Qt documentation is a set of rules that Qt developers guarantee, if your application does not comply with them then it might work well for a certain version of Qt but in a next version it may not work. The rules of Qt are guarantees of stability, that is to say, Qt every time you make a change will monitor that they continue to be followed. Many times I have seen applications that implement certain functionality that do not comply with the rules and when they update Qt: boom, it breaks. – eyllanesc Feb 17 '20 at 00:03
  • The idea that the GUI should not be modified in another thread is because most GUI elements are not thread-safe, but there are parts that are like the signals. Why does Qt not make the entire GUI thread-safe? because for a variable to be thread-safe it is necessary to use mutex, semaphores, atomic variables, etc. that bring a problem: the time consumed by the synchronization task, and the GUI handles many variables so it would be prone to create bottlenecks , so many GUI and Qt point out that basic rule. – eyllanesc Feb 17 '20 at 00:07
  • On the other hand you are abusing the global variables, the global variables are not thread-safe and processing-safe (for this there are queues, mutex, semaphores, etc.) so you can have an undefined behavior that will make your SW unstable. – eyllanesc Feb 17 '20 at 00:09
  • What do you mean by modified in another thread? My gui is always created and modified in a unique thread of the process. This is why I have phrased my questions in a very precise way? And this is precisely because the GUI elements are non thread safe that I want to be able to kill the GUI thread without stopping my main event loop that should survive the crash of the GUI. – user3650925 Feb 17 '20 at 00:15
  • You must create the GUI in the main thread of any process, but not in a secondary thread – eyllanesc Feb 17 '20 at 00:17
  • OK, it is a yes to question 1. But question 3: what is the main thread? The thread that runs the Process.run() top event loop? And question 4: is the meaning of main thread the same from the Process point of view and from the Qt point of view? A simple yes or no answer to each question (except 2) will make things clear. And an answer to 2 would be even better. In my pyQt4 QApplication doc, I don't find this rule. – user3650925 Feb 17 '20 at 00:24
  • @user3650925 note that a crashing thread will crash the entire process (taking out any other threads in the process with it) and unilaterally killing a thread will often leave the process in an unstable state (e.g. with mutexes or other resources allocated by that thread left in a locked/allocated/unusable state due to the killed threads' inability to release them). If you want to be able to crash/kill your GUI code without affecting your main event loop, the proper way to do that is to run the GUI code in a child *process*, not inside a child thread. – Jeremy Friesner Feb 17 '20 at 03:25
  • Thanks, Jeremy, but it is not the case in my IDE. I have a KillableThread class deriving from Thread, and I can kill a particular thread (even when it is running an infinite loop) without crashing the entire process P2. And the reason we have not defined the GUI code in a separate process is the need for speed. The Qt GUI access the variables of the Process created and modified by the other threads in a very fast way... – user3650925 Feb 17 '20 at 08:06

2 Answers2

0

Many elements of answer to the questions asked here can be found there (see both the answers and comments, from JKSH in particular).

To summarize, the meaning of "main thread" can be different for Qt and for the system, depending on the platform. On linux and windows, the main thread for Qt is simply the first thread that has something to do with Qt (be careful with the imports in all other threads). It is not the thread that has run process.run(). In OS X, the situation is different and the "main thread" is the same for Qt and for the process. In that respect, the documentation seems to be considered as innacurate by the specialists...

So tentative answers to the precise questions of this post are:

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

=> No

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

=> None, because "main thread" is never defined correctly in the pyQt4 documentation

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

=> Yes

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

=> It is the thread referring to the first Qt object (be carefull with the imports in the process' main thread). It is not the main thread of the process. They can be different.

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

=> Because it is created in what Qt calls the "main thread", although it is different from what the process calls the "main thread".

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

=> It simply does NOT on windows and linux.

Please correct this answer in case it contains any error.

user3650925
  • 173
  • 1
  • 10
0

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')
user3650925
  • 173
  • 1
  • 10