Skip to content Skip to sidebar Skip to footer

Running A Long Python Calculation In A Thread, With Logging To A Qt Window, Crashes After A Short While

I have a large module from a separate project, which I wanted to integrate into a GUI. The module does some calculations that take a couple of minutes, and I want to keep the GUI r

Solution 1:

Following the hint by G.M., I have made a version that I think obeys the rules of Qt. I made a ThreadLogger(logging.Handler) class, which I set to handle all logs in the Worker thread, and send them to the main thread via slots and signals.

I kept getting the error TypeError: emit() takes 2 positional arguments but 3 were given when I inherited QtCore.QObject (and logging.Handler) in ThreadLogger, which I suspect was because I was overriding QtCore.Signal.emit() So I put the Signal in a separate MyLog(QObject) class, and just use an instance of that in ThreadLogger

classMyLog(QtCore.QObject):
    # create a new Signal# - have to be a static element# - class  has to inherit from QObject to be able to emit signals
    signal = QtCore.Signal(str)

    # not sure if it's necessary to implement thisdef__init__(self):
        super().__init__()

And here is the ThreadLogger(logging.Handler) class. This just emits all logs via signal in MyLog above

# custom logging handler that can run in separate thread, and emit all logs# via signals/slots so they can be used to update the GUI in the main threadclassThreadLogger(logging.Handler):def__init__(self):
        super().__init__()
        self.log = MyLog()

    # logging.Handler.emit() is intended to be implemented by subclassesdefemit(self, record):
        msg = self.format(record)
        self.log.signal.emit(msg)

The complete code is

import sys
import logging
import numpy as np
from PySide2 import QtWidgets, QtCore


deflongFunction(logger):
    logger.info("Start long running function")
    i = 0whileTrue:
        for j inrange(10000):
            t = np.arange(256)
            sp = np.fft.fft(np.sin(t))
            freq = np.fft.fftfreq(t.shape[-1])
            sp = sp + freq
        logger.info("%d" % i)
        i += 1# since I don't really need an event thread, I subclass QThread, as per# https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.htmlclassWorker(QtCore.QThread):
    def__init__(self, parent=None):
        super().__init__(parent)

        ## set up logging# __init__ is run in the thread that creates this thread, not in the# new thread. But logging is thread-safe, so I don't think it matters# create logger for this class
        self.logger = logging.getLogger("Worker")

        # set up log handler
        self.logHandler = ThreadLogger()
        self.logHandler.setFormatter(
            logging.Formatter('%(asctime)s - %(levelname)s - %(threadName)s - %(message)s'))
        self.logger.addHandler(self.logHandler)

        # set the logging level
        self.logger.setLevel(logging.DEBUG)

    defrun(self):
        longFunction(self.logger)


classMyLog(QtCore.QObject):
    # create a new Signal# - have to be a static element# - class  has to inherit from QObject to be able to emit signals
    signal = QtCore.Signal(str)

    # not sure if it's necessary to implement thisdef__init__(self):
        super().__init__()


# custom logging handler that can run in separate thread, and emit all logs# via signals/slots so they can be used to update the GUI in the main threadclassThreadLogger(logging.Handler):
    def__init__(self):
        super().__init__()
        self.log = MyLog()

    # logging.Handler.emit() is intended to be implemented by subclassesdefemit(self, record):
        msg = self.format(record)
        self.log.signal.emit(msg)


classMyWidget(QtWidgets.QDialog):
    def__init__(self, parent=None):
        super().__init__(parent)

        # read-only text box
        self.logTextBox = QtWidgets.QPlainTextEdit(self)
        self.logTextBox.setReadOnly(True)

        self.resize(400, 500)

        # start button
        self.startButton = QtWidgets.QPushButton(self)
        self.startButton.setText('Start')

        # connect start button
        self.startButton.clicked.connect(self.start)

        # set up layout
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.logTextBox)
        layout.addWidget(self.startButton)
        self.setLayout(layout)

    defstart(self):
        self.thread = Worker()
        self.thread.finished.connect(self.threadFinished)
        self.thread.start()

        # we don't want to enable user to start another thread while this one# is running so we disable the start button.
        self.startButton.setEnabled(False)

        # connect logger
        self.thread.logHandler.log.signal.connect(self.write_log)

    defthreadFinished(self):
        self.startButton.setEnabled(True)

    # define a new Slot, that receives a string    @QtCore.Slot(str)defwrite_log(self, log_text):
        self.logTextBox.appendPlainText(log_text)
        self.logTextBox.centerCursor()  # scroll to the bottom


app = QtWidgets.QApplication(sys.argv)
w = MyWidget()
w.show()
app.exec_()

I am not quite sure why yet, but I get all logs from longFunction in the terminal as well as in the GUI window (but with different formats). I am also not 100% if this is actually thread-safe, and obeys all Qt threading rules, but at least it doesn't crash in the same way as before.

I will leave this answer up for a couple of days, and accept it then if I don't get any better answers, or it turns out my solution is wrong!

Post a Comment for "Running A Long Python Calculation In A Thread, With Logging To A Qt Window, Crashes After A Short While"