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:
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:
Is the code above violating any Qt4 rule because it starts a QApplication in a non-main thread?
Which page of the QT4 documentation tells that it is forbidden to process like we have done?
What is the definition of the main thread of a process? The thread that runs the process.run() function?
What is a Qt main thread? Is it by definition the main thread of the process or can it be another thread?
Why don't we have an error "QApplication was not created in the main thread"?
How does Qt4 recognize whether the event loop is running or not in the main thread of a Process?
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')