6

I'm trying to find a way to quit my app properly. When I exit, I get an error saying QThread: Destroyed while thread is still running. I have a thread for feeding output to a QTextBrowser. What should be the proper way to exit? Here's what I've got:

class LogReceiver(QtCore.QObject):
    mysignal = QtCore.Signal(str)

    def __init__(self, queue, *args, **kwargs):
        QtCore.QObject.__init__(self, *args, **kwargs)
        self.queue = queue

    def run(self):
        while True:
            text = self.queue.get()
            self.mysignal.emit(text)

if __name__ == '__main__':
    queue = Queue()
    thread = QtCore.QThread()
    my_receiver = MyReceiver(queue)

    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    my_receiver.mysignal.connect(window.append_text)
    my_receiver.moveToThread(thread)
    thread.started.connect(my_receiver.run)
    thread.start()

    sys.exit(app.exec_())

Should thread somehow be terminated upon exit? Note that self.queue.get() blocks and waits for text.

Thanks

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
Kar
  • 6,063
  • 7
  • 53
  • 82

2 Answers2

3

You need to re-structure the while loop so that it doesn't block uncondtionally.

You can do this with a simple flag and a timeout:

    def run(self):
        self.active = True
        while self.active:
            try:
                text = self.queue.get(timeout=1.0)
                self.mysignal.emit(text)
            except Empty:
                continue

So now the queue won't block indefinitely, and the flag will be checked once a second to see if the loop should be exited.

EDIT:

Here's a working example based on your code:

import sys
from queue import Queue, Empty
from PySide import QtCore, QtGui

class LogReceiver(QtCore.QObject):
    mysignal = QtCore.Signal(str)

    def __init__(self, queue, *args, **kwargs):
        QtCore.QObject.__init__(self, *args, **kwargs)
        self.queue = queue

    def run(self):
        self.active = True
        while self.active:
            try:
                text = self.queue.get(timeout=1.0)
                self.mysignal.emit('text')
            except Empty:
                continue
        print('finished')

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.queue = Queue()
        self.thread = QtCore.QThread(self)
        self.receiver = LogReceiver(self.queue)
        self.receiver.moveToThread(self.thread)
        self.thread.started.connect(self.receiver.run)
        self.thread.start()

    def closeEvent(self, event):
        print('close')
        self.receiver.active = False
        self.thread.quit()
        self.thread.wait()

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Using prajmus's second solution along with the argument `timeout=1.0`, it'd get stuck at `thread.wait()` though. – Kar Feb 25 '15 at 20:18
  • @Kate. I don't think any of those solutions are going to work, because they try to operate on the thread after the main event-loop has terminated. I've added a working example to my answer to show how my solution should be used. – ekhumoro Feb 25 '15 at 21:03
2

Try:

# previous code here
thread.start()
app.exec_()

thread.terminate()
thread.wait()
sys.exit(0)

Basically when exec_() finishes (QApplication is closed ex. by closing the window) you force the thread to terminate and wait() for it to cleanup. If your thread has an event loop you can call quit() instead of terminate(). terminate() is generally not a good idea see: here.

The more desirable approach would be to put a flag in run() method ex.

while !flag:
    do stuff

and change main to:

app.exec_()
flag = True
thread.wait()
sys.exit(0)

Where flag is a global variable. QThread terminates itself when run() method finishes.

Community
  • 1
  • 1
prajmus
  • 3,171
  • 3
  • 31
  • 41
  • Thanks. I'm not sure if I can run `thread.wait()` because the thread is blocking. I've updated my question with how it blocks. – Kar Feb 25 '15 at 10:01
  • The way I understand the second solution (with flag) is that `run()` needs to reach the while condition in order for the loop to break. If `text = self.queue.get()` is already blocking when `flag = True` is set, the thread would still be blocked indefinitely. – Kar Feb 25 '15 at 10:22
  • @Kate yes you're right, sorry I typed faster than I thought :) – prajmus Feb 25 '15 at 10:25
  • 1
    @Kate can you do it nonblocking way? like while flag: while queue is not empty get text and emit signal. (second while inside the one you already have) – prajmus Feb 25 '15 at 10:30