tweak-wtf
tweak-wtf

Reputation: 63

Gracefully shutdown QThread in PySide2

So I'm writing an application using PySide2 that shall redirect everything from stdout to an intermediate queue.Queue. That Queue emits a signal that will be processed by a QThread, appending all strings in the queue to a QTextEdit.

I have looked around SO quite a bit but unfortunately nothing really seems to work for me.

That's the code I'm referring to.

from PySide2 import QtWidgets, QtGui, QtCore

import sys
from queue import Queue


class WriteStream(object):
    """ Redirects sys.stdout to a thread-safe Queue

    Arguments:
        object {object} -- base class
    """
    def __init__(self, queue):
        self.queue = queue

    def write(self, msg):
        self.queue.put(msg)

    def flush(self):
        """ Passing to create non-blocking stream (?!)
            https://docs.python.org/3/library/io.html#io.IOBase.flush
        """
        pass


class WriteStreamThread(QtCore.QThread):
    queue_updated = QtCore.Signal(str)

    def __init__(self, queue):
        super(WriteStreamThread, self).__init__()
        self.stop = False
        self.queue = queue

    def set_stop(self):
        self.stop = True
        self.wait() # waits till finished signal has been emitted

    def run(self):
        while not self.stop:    # i guess this is blocking
            msg = self.queue.get()
            self.queue_updated.emit(msg)
            self.sleep(1)   # if commented out, app crashes
        self.finished.emit()


class Verifyr(QtWidgets.QMainWindow):
    def __init__(self, queue, parent=None):
        super(Verifyr, self).__init__(parent)
        self.centralwidget = QtWidgets.QWidget(self)
        layout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.textedit = QtWidgets.QTextEdit(self)
        layout.addWidget(self.textedit)
        self.setLayout(layout)
        self.setCentralWidget(self.centralwidget)
        print("Verifyr initialised...")

        self.listener_thread = WriteStreamThread(queue)
        self.listener_thread.queue_updated.connect(self._log_to_qtextedit)
        self.listener_thread.start()

    @QtCore.Slot(str)
    def _log_to_qtextedit(self, msg):
        self.textedit.insertPlainText(msg)


if __name__ == '__main__':
    # create Queue to be passed to WriteStream and WriteStreamListener
    queue = Queue()
    # redirect stdout to WriteStream()
    sys.stdout = WriteStream(queue)
    print("Redirected sys.stdout to WriteStream")

    # launching the app
    app = QtWidgets.QApplication(sys.argv)
    window = Verifyr(queue)
    app.aboutToQuit.connect(window.listener_thread.set_stop)
    window.show()
    sys.exit(app.exec_())

In WriteStreamThread I'm using a while loop that keeps emitting signals, caught by the main application to append to its QTextEdit. When I comment out the self.sleep(1) the application ill be stuck in an infinite loop. If not the thread will exit out just fine.

Can somebody please explain this to me?

UPDATE: like I mentioned in my comment I've found the bug... here's the updated run() method of WriteStreamThread:

def run(self):
    while not self.stop:
        try:
            msg = self.queue.get(block=False) # nasty little kwarg
        except:
            msg = "No items in queue. Sleeping 1sec.."
            self.sleep(1)
        self.queue_updated.emit(msg)
    self.finished.emit()    # optional

Upvotes: 0

Views: 370

Answers (1)

tweak-wtf
tweak-wtf

Reputation: 63

I found the bug. It was queue.get() in the run() method of WriteStreamThread that was blocking. Changing it to queue.get(block=False) and surrounded with try/catch does the job. Stupid me...

Upvotes: 1

Related Questions