R01k R01k - 1 month ago 17
Python Question

Why does input() cause "QCoreApplication::exec: The event loop is already running"?

I have run into this QCoreApplication problem where invoking input() after a QObject finishes executing inside a QThread causes an infinite loop printing to the console "QCoreApplication::exec: The event loop is already running".

In the code I create a generic worker as a QObject, move it into a QThread (the sanctioned way to use QThread, instead of subclassing it) and then execute another QObject's (Master class) function inside the generic worker. Everything works fine as long as I don't call input() after the Master has been executed. Note that the problem occurs also if I execute a function directly in the worker (not a Master instance's function).

Here is the sample code to reproduce the problem:

import sys
from PyQt4.QtCore import QCoreApplication, QObject, QThread, pyqtSignal, pyqtSlot


class Worker(QObject):
"""
Generic worker.
"""
start = pyqtSignal(str)
finished = pyqtSignal()

def __init__(self, function):
QObject.__init__(self)
self._function = function
self.start.connect(self.run)

def run(self):
self._function()
self.finished.emit()


class Master(QObject):
"""
An object that will use the worker class.
"""
finished = pyqtSignal()

def __init__(self):
QObject.__init__(self)

@pyqtSlot()
def do(self):
print("Do what?")
self.finished.emit()


def done():
# FIXME This will cause an infinite loop printing to the console:
# "QCoreApplication::exec: The event loop is already running"
input("Enter your answer: ")


def main():
app = QCoreApplication(sys.argv)

master = Master()
worker = Worker(master.do)
master.finished.connect(done)

thread = QThread()
thread.started.connect(worker.run)
worker.moveToThread(thread)

# Terminating thread gracefully, or so.
worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)

thread.start()

sys.exit(app.exec_())


if __name__ == "__main__":
main()

Answer

There is no real problem with input in your example. After pressing enter in done(), control will return to the event-loop and then wait for further user interaction - which is the normal and expected behaviour.

You don't make it clear what you expect to happen after that. But if you want the program to quit, just do this:

def done():
    input("Enter your answer: ")
    QCoreApplication.quit()

The Qt warning message is harmless, but it can be removed like this:

def main():
    from PyQt4.QtCore import pyqtRemoveInputHook
    pyqtRemoveInputHook()

    app = QCoreApplication(sys.argv)
    ...

The only real problem in your example is the threading implementation. If you add the line print(QThread.currentThread()) to Worker.run(), Master.do() and main(), you will see that all three are executed in the main thread. This is because you connected the thread.start signal before moving the worker to the other thread. The best (i.e. most easily maintainable) way to fix this issue to always use the @pyqtSlot decorator on any slots that are connected across threads - because then it won't matter when the signal connections are made. (See this answer for a more complete explanation of this issue).